diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 59c238ec058a5..17150e3c98cec 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -39,6 +39,7 @@ /src/plugins/visualize/ @elastic/kibana-vis-editors /src/plugins/visualizations/ @elastic/kibana-vis-editors /src/plugins/chart_expressions/expression_tagcloud/ @elastic/kibana-vis-editors +/src/plugins/chart_expressions/expression_metric/ @elastic/kibana-vis-editors /src/plugins/url_forwarding/ @elastic/kibana-vis-editors /packages/kbn-tinymath/ @elastic/kibana-vis-editors /x-pack/test/functional/apps/lens @elastic/kibana-vis-editors diff --git a/.i18nrc.json b/.i18nrc.json index 707c1cef2594b..63e4cf6d2fbb9 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -30,6 +30,7 @@ "expressionRevealImage": "src/plugins/expression_reveal_image", "expressionShape": "src/plugins/expression_shape", "expressionTagcloud": "src/plugins/chart_expressions/expression_tagcloud", + "expressionMetricVis": "src/plugins/chart_expressions/expression_metric", "inputControl": "src/plugins/input_control_vis", "inspector": "src/plugins/inspector", "inspectorViews": "src/legacy/core_plugins/inspector_views", diff --git a/api_docs/charts.json b/api_docs/charts.json index 642ae89e581e5..9f6d07287eba1 100644 --- a/api_docs/charts.json +++ b/api_docs/charts.json @@ -478,7 +478,7 @@ "signature": [ "(value: any, colorSchemaName: string) => string" ], - "path": "src/plugins/charts/public/static/color_maps/heatmap_color.ts", + "path": "src/plugins/charts/common/static/color_maps/heatmap_color.ts", "deprecated": false, "children": [ { @@ -491,7 +491,7 @@ "signature": [ "any" ], - "path": "src/plugins/charts/public/static/color_maps/heatmap_color.ts", + "path": "src/plugins/charts/common/static/color_maps/heatmap_color.ts", "deprecated": false, "isRequired": true }, @@ -505,7 +505,7 @@ "signature": [ "string" ], - "path": "src/plugins/charts/public/static/color_maps/heatmap_color.ts", + "path": "src/plugins/charts/common/static/color_maps/heatmap_color.ts", "deprecated": false, "isRequired": true } @@ -936,7 +936,7 @@ "tags": [], "label": "ColorMap", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "children": [ { @@ -949,7 +949,7 @@ "signature": [ "any" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false } ], @@ -962,7 +962,7 @@ "tags": [], "label": "ColorSchema", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "children": [ { @@ -981,7 +981,7 @@ "text": "ColorSchemas" } ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -991,7 +991,7 @@ "tags": [], "label": "text", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false } ], @@ -1817,7 +1817,7 @@ "tags": [], "label": "RawColorSchema", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "children": [ { @@ -1836,7 +1836,7 @@ "text": "ColorSchemas" } ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -1846,7 +1846,7 @@ "tags": [], "label": "label", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -1859,7 +1859,7 @@ "signature": [ "[number, number[]][]" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false } ], @@ -2011,7 +2011,7 @@ "tags": [], "label": "ColorSchemas", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "initialIsOpen": false } @@ -2027,7 +2027,7 @@ "signature": [ "\"Background\" | \"Labels\" | \"None\"" ], - "path": "src/plugins/charts/public/static/components/collections.ts", + "path": "src/plugins/charts/common/static/components/collections.ts", "deprecated": false, "initialIsOpen": false }, @@ -2048,7 +2048,7 @@ }, "[]" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "initialIsOpen": false }, @@ -2059,7 +2059,7 @@ "tags": [], "label": "defaultCountLabel", "description": [], - "path": "src/plugins/charts/public/static/components/collections.ts", + "path": "src/plugins/charts/common/static/components/collections.ts", "deprecated": false, "initialIsOpen": false }, @@ -2073,7 +2073,7 @@ "signature": [ "number" ], - "path": "src/plugins/charts/public/static/components/collections.ts", + "path": "src/plugins/charts/common/static/components/collections.ts", "deprecated": false, "initialIsOpen": false }, @@ -2124,7 +2124,7 @@ }, "[]" ], - "path": "src/plugins/charts/public/static/color_maps/truncated_color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/truncated_color_maps.ts", "deprecated": false, "initialIsOpen": false } @@ -2140,7 +2140,7 @@ "signature": [ "{ readonly Background: \"Background\"; readonly Labels: \"Labels\"; readonly None: \"None\"; }" ], - "path": "src/plugins/charts/public/static/components/collections.ts", + "path": "src/plugins/charts/common/static/components/collections.ts", "deprecated": false, "initialIsOpen": false }, @@ -2154,7 +2154,7 @@ "signature": [ "{ readonly Horizontal: number; readonly Vertical: number; readonly Angled: number; }" ], - "path": "src/plugins/charts/public/static/components/collections.ts", + "path": "src/plugins/charts/common/static/components/collections.ts", "deprecated": false, "initialIsOpen": false }, @@ -2165,7 +2165,7 @@ "tags": [], "label": "truncatedColorMaps", "description": [], - "path": "src/plugins/charts/public/static/color_maps/truncated_color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/truncated_color_maps.ts", "deprecated": false, "children": [], "initialIsOpen": false @@ -2177,7 +2177,7 @@ "tags": [], "label": "vislibColorMaps", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "children": [ { @@ -2189,7 +2189,7 @@ "description": [ "// Sequential" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "children": [ { @@ -2209,7 +2209,7 @@ }, ".Blues" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2219,7 +2219,7 @@ "tags": [], "label": "label", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2232,7 +2232,7 @@ "signature": [ "[number, number[]][]" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false } ] @@ -2244,7 +2244,7 @@ "tags": [], "label": "[ColorSchemas.Greens]", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "children": [ { @@ -2264,7 +2264,7 @@ }, ".Greens" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2274,7 +2274,7 @@ "tags": [], "label": "label", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2287,7 +2287,7 @@ "signature": [ "[number, number[]][]" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false } ] @@ -2299,7 +2299,7 @@ "tags": [], "label": "[ColorSchemas.Greys]", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "children": [ { @@ -2319,7 +2319,7 @@ }, ".Greys" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2329,7 +2329,7 @@ "tags": [], "label": "label", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2342,7 +2342,7 @@ "signature": [ "[number, number[]][]" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false } ] @@ -2354,7 +2354,7 @@ "tags": [], "label": "[ColorSchemas.Reds]", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "children": [ { @@ -2374,7 +2374,7 @@ }, ".Reds" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2384,7 +2384,7 @@ "tags": [], "label": "label", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2397,7 +2397,7 @@ "signature": [ "[number, number[]][]" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false } ] @@ -2409,7 +2409,7 @@ "tags": [], "label": "[ColorSchemas.YellowToRed]", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "children": [ { @@ -2429,7 +2429,7 @@ }, ".YellowToRed" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2439,7 +2439,7 @@ "tags": [], "label": "label", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2452,7 +2452,7 @@ "signature": [ "[number, number[]][]" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false } ] @@ -2464,7 +2464,7 @@ "tags": [], "label": "[ColorSchemas.GreenToRed]", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false, "children": [ { @@ -2484,7 +2484,7 @@ }, ".GreenToRed" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2494,7 +2494,7 @@ "tags": [], "label": "label", "description": [], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false }, { @@ -2507,7 +2507,7 @@ "signature": [ "[number, number[]][]" ], - "path": "src/plugins/charts/public/static/color_maps/color_maps.ts", + "path": "src/plugins/charts/common/static/color_maps/color_maps.ts", "deprecated": false } ] diff --git a/dev_docs/contributing/code_walkthrough.mdx b/dev_docs/contributing/code_walkthrough.mdx new file mode 100644 index 0000000000000..47eb05a95c424 --- /dev/null +++ b/dev_docs/contributing/code_walkthrough.mdx @@ -0,0 +1,139 @@ +--- +id: kibRepoStructure +slug: /kibana-dev-docs/contributing/repo-structure +title: Repository structure +summary: High level walk-through of our repository structure. +date: 2021-10-07 +tags: ['contributor', 'dev', 'github', 'getting started', 'onboarding', 'kibana'] +--- + +A high-level walk through of the folder structure of our [repository](https://github.com/elastic/kibana). + +Tip: Look for a `README.md` in a folder to learn about its contents. + +## [.buildkite](https://github.com/elastic/kibana/tree/master/.buildkite) + +Managed by the operations team to set up a new buildkite ci system. Can be ignored by folks outside the Operations team. + +## [.ci](https://github.com/elastic/kibana/tree/master/.ci) + +Managed by the operations team to contain Jenkins settings. Can be ignored by folks outside the Operations team. + +## [.github](https://github.com/elastic/kibana/tree/master/.github) + +Contains GitHub configuration settings. This file contains issue templates, and the [CODEOWNERS](https://github.com/elastic/kibana/blob/master/.github/CODEOWNERS) file. It's important for teams to keep the CODEOWNERS file up-to-date so the right team is pinged for a code owner review on PRs that edit certain files. Note that the `CODEOWNERS` file only exists on the main/master branch, and is not backported to other branches in the repo. + +## [api_docs](https://github.com/elastic/kibana/tree/master/api_docs) + +Every file in here is auto-generated by the and used to render our API documentation. If you edit a public plugin or package API and run `node scripts/build_api_docs` you will see files changed in this folder. Do not edit the contents of this folder directly! + +Note that currently you may see _a lot_ of changes because that command is not run on every PR and so the content in this folder is often outdated. + +## [config](https://github.com/elastic/kibana/tree/master/config) + +This contains the base configuration file, `kibana.yml`. If you want to tweak any settings, create a `kibana.dev.yml` in here which will get picked up during development, and not checked into GitHub. + +## [docs](https://github.com/elastic/kibana/tree/master/docs) + +Every folder in here _except_ the [development one](https://github.com/elastic/kibana/tree/master/docs/development) contains manually generated asciidocs that end up hosted in our [Elastic guide](https://www.elastic.co/guide/). + +The `development` folder contains markdown that is auto-generated with our legacy API docs tool. We are aiming to remove it shortly after 8.0FF. + +## [dev_docs](https://github.com/elastic/kibana/tree/master/dev_docs) + +This is where a lot of manually written content for our Developer Guide resides. Developers may also keep the information closer to what it's describing, but it's a good spot for high-level information, or information that describes how multiple plugins work together. + +## [examples](https://github.com/elastic/kibana/tree/master/examples) + +These are our tested example plugins that also get built and hosted [here](https://demo.kibana.dev/8.0/app/developerExamples). If a plugin is written for testing purposes only, it won't go in here. These example plugins should be written with the intention of helping developers understand how to use our services. + +## [legacy_rfcs](https://github.com/elastic/kibana/tree/master/legacy_rfcs) + +We used to write RFCs in `md` format and keep them in the repository, but we have since moved to Google Docs. We kept the folder around, since some folks still read these old docs. If you are an internal contributor you can visit to read about our current RFC process. + +## [licenses](https://github.com/elastic/kibana/tree/master/licenses) + +Contains our two license header texts, one for the Elastic license and one for the Elastic+SSPL license. All code files inside x-pack should have the Elastic license text at the top, all code files outside x-pack should contain the other. If you have your environment set up to auto-fix on save, eslint should take care of adding it for you. If you don't have it, ci will fail. Can be ignored for the most part, this rarely changes. + +## [packages](https://github.com/elastic/kibana/tree/master/packages) + +The packages folder contains a mixture of build-time related code (like the [code needed to build the api docs](https://github.com/elastic/kibana/tree/master/packages/kbn-docs-utils)), as well as static code that some plugins rely on (like the [kbn-monaco package](https://github.com/elastic/kibana/tree/master/packages/kbn-monaco)). covers how packages differ from plugins. + +## [plugins](https://github.com/elastic/kibana/tree/master/plugins) + +This is an empty folder in GitHub. It's where third party developers should put their plugin folders. Internal developers can ignore this folder. + +## [scripts](https://github.com/elastic/kibana/tree/master/scripts) + +Contains a bunch of developer scripts. These are usually very small files with just two lines that kick off a command, the logic of which resides elsewhere (sometimes `src/dev`, sometimes inside `packages`). +Example: +``` +require('../src/setup_node_env'); +require('@kbn/es-archiver').runCli(); +``` + +## [src](https://github.com/elastic/kibana/tree/master/src) + +This folder and the packages folder contain the most code and where developers usually find themselves. I'll touch on a few of the subfolder, the rest can generally be ignored, or are build/ops related code. + +### [src/cli*](https://github.com/elastic/kibana/tree/master/src/cli) + +Maintained primarily by the Operations team, this folder contains code that initializes the Kibana runtime and a bit of work to handle authorization for interactive setup mode. Most devs should be able to ignore this code. + +### [src/core](https://github.com/elastic/kibana/tree/master/src/core) + +This code primarily belongs to the Core team and contains the plugin infrastructure, as well as a bunch of fundamental services like migrations, saved objects, and some UI utilities (toasts, flyouts, etc.). + +### [src/dev](https://github.com/elastic/kibana/tree/master/src/dev) + +Maintained by the Operations team, this code contains build and development tooling related code. This folder existed before `packages`, so contains mostly older code that hasn't been migrated to packages. Prefer creating a `package` if possible. Can be ignored for the most part if you are not on the Ops team. Prefer + +### [src/plugins](https://github.com/elastic/kibana/tree/master/src/plugins) + +Contains all of our Basic-licensed plugins. Most folders in this directory will contain `README.md` files explaining what they do. If there are none, you can look at the `owner.gitHub` field inside all `kibana.json`s that will tell you which team to get in touch with for questions. + +Note that as plugins can be nested, each folder in this directory may contain multiple plugins. + +## [test](https://github.com/elastic/kibana/tree/master/test) + +Contains functional tests and related FTR (functional test runner) code for the plugins inside `src/plugins`, although there is a push to move the tests to reside inside the plugins themselves. + +## [typings](https://github.com/elastic/kibana/tree/master/typings) + +Maintained by Ops and Core, this contains global typings for dependencies that do not provide their own types, or don't have types available via [DefinitelyTyped](https://definitelytyped.org). This directory is intended to be minimal; types should only be added here as a last resort. + +## [vars](https://github.com/elastic/kibana/tree/master/vars) + +A bunch of groovy scripts maintained by the Operations team. + +## [x-pack](https://github.com/elastic/kibana/tree/master/x-pack) + +Contains all code and infrasturcture that powers our gold+ (non-basic) features that are provided under a more restrictive license. + +### [x-pack/build_chromium](https://github.com/elastic/kibana/tree/master/x-pack/build_chromium) + +Maintained by the App Services UX team, this contains Reporting-related code for building Chromium in order to take server side screenshots. + +### [x-pack/dev-tools](https://github.com/elastic/kibana/tree/master/x-pack/dev-tools) + +Maintained by the Operations team. + +### [x-pack/examples](https://github.com/elastic/kibana/tree/master/x-pack/examples) + +Contains all example plugins that rely on gold+ features. + +### [x-pack/plugins](https://github.com/elastic/kibana/tree/master/x-pack/plugins) + +Contains code for all the plugins that power our gold+ features. + +### [x-pack/scripts](https://github.com/elastic/kibana/tree/master/x-pack/scripts) + +Maintained by the Ops team, this folder contains some scripts for running x-pack utilities, like the functional test runner that runs with a license higher than Basic. + +### [x-pack/test](https://github.com/elastic/kibana/tree/master/x-pack/test) + +Functional tests for our gold+ features. + + + + diff --git a/dev_docs/key_concepts/navigation.mdx b/dev_docs/key_concepts/navigation.mdx index 85b0fe8429a54..27ba3db111411 100644 --- a/dev_docs/key_concepts/navigation.mdx +++ b/dev_docs/key_concepts/navigation.mdx @@ -50,7 +50,7 @@ console.log(discoverUrl); // http://localhost:5601/bpr/s/space/app/discover const discoverUrlWithSomeState = core.http.basePath.prepend(`/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:'2020-09-10T11:39:50.203Z',to:'2020-09-10T11:40:20.249Z'))&_a=(columns:!(_source),filters:!(),index:'90943e30-9a47-11e8-b64d-95841ca0b247',interval:auto,query:(language:kuery,query:''),sort:!())`); ``` -Instead, each app should expose {kib-repo}tree/{branch}/src/plugins/share/common/url_service/locators/README.md[a locator]. +Instead, each app should expose [a locator](https://github.com/elastic/kibana/blob/master/src/plugins/share/common/url_service/locators/README.md). Other apps should use those locators for navigation or URL creation. ```tsx @@ -114,8 +114,7 @@ const MySPALink = () => As it would be too much boilerplate to do this for each link in your app, there is a handy wrapper that helps with it: [RedirectAppLinks](https://github.com/elastic/kibana/blob/master/src/plugins/kibana_react/public/app_links/redirect_app_link.tsx#L49). -[source,typescript jsx] ----- +```jsx const MyApp = () => {/*...*/} @@ -123,7 +122,7 @@ const MyApp = () => Go to Dashboard {/*...*/} ----- +``` ## Setting up internal app routing @@ -167,7 +166,7 @@ Common use-case for using `core`'s `ScopedHistory` directly: ## Syncing state with URL Historically Kibana apps store _a lot_ of application state in the URL. -The most common pattern that {kib} apps follow today is storing state in `_a` and `_g` query params in [rison](https://github.com/w33ble/rison-node#readme) format. +The most common pattern that Kibana apps follow today is storing state in `_a` and `_g` query params in [rison](https://github.com/w33ble/rison-node#readme) format. Those query params follow the convention: diff --git a/docs/developer/advanced/images/sharing-saved-objects-faq-multiple-deep-link-objects-1.png b/docs/developer/advanced/images/sharing-saved-objects-faq-multiple-deep-link-objects-1.png new file mode 100644 index 0000000000000..d0fd93574d2fa Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-faq-multiple-deep-link-objects-1.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-faq-multiple-deep-link-objects-2.png b/docs/developer/advanced/images/sharing-saved-objects-faq-multiple-deep-link-objects-2.png new file mode 100644 index 0000000000000..c959042ab8d53 Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-faq-multiple-deep-link-objects-2.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-step-3.png b/docs/developer/advanced/images/sharing-saved-objects-step-3.png index 92dd7ebfef88e..482b5f4e93a4a 100644 Binary files a/docs/developer/advanced/images/sharing-saved-objects-step-3.png and b/docs/developer/advanced/images/sharing-saved-objects-step-3.png differ diff --git a/docs/developer/advanced/sharing-saved-objects.asciidoc b/docs/developer/advanced/sharing-saved-objects.asciidoc index 06019735188aa..8a0373363e473 100644 --- a/docs/developer/advanced/sharing-saved-objects.asciidoc +++ b/docs/developer/advanced/sharing-saved-objects.asciidoc @@ -235,9 +235,7 @@ export class MyPlugin implements Plugin<{}, {}, {}, PluginStartDeps> { if (spacesApi && resolveResult.outcome === 'aliasMatch') { // We found this object by a legacy URL alias from its old ID; redirect the user to the page with its new ID, preserving any URL hash const newObjectId = resolveResult.alias_target_id!; // This is always defined if outcome === 'aliasMatch' - const newPath = http.basePath.prepend( - `path/to/this/page/${newObjectId}${window.location.hash}` - ); + const newPath = `/this/page/${newObjectId}${window.location.hash}`; // Use the *local* path within this app (do not include the "/app/appId" prefix) await spacesApi.ui.redirectLegacyUrl(newPath, OBJECT_NOUN); return; } @@ -255,9 +253,7 @@ const getLegacyUrlConflictCallout = () => { // callout with a warning for the user, and provide a way for them to navigate to the other object. const currentObjectId = savedObject.id; const otherObjectId = resolveResult.alias_target_id!; // This is always defined if outcome === 'conflict' - const otherObjectPath = http.basePath.prepend( - `path/to/this/page/${otherObjectId}${window.location.hash}` - ); + const otherObjectPath = `/this/page/${otherObjectId}${window.location.hash}`; // Use the *local* path within this app (do not include the "/app/appId" prefix) return ( <> {spacesApi.ui.components.getLegacyUrlConflict({ @@ -391,6 +387,13 @@ These should be handled on a case-by-case basis at the plugin owner's discretion * Any "secondary" objects on the page may handle the outcomes differently. If the secondary object ID is not important (for example, it just functions as a page anchor), it may make more sense to ignore the different outcomes. If the secondary object _is_ important but it is not directly represented in the UI, it may make more sense to throw a descriptive error when a `'conflict'` outcome is encountered. + - Embeddables should use `spacesApi.ui.components.getEmbeddableLegacyUrlConflict` to render conflict errors: ++ +image::images/sharing-saved-objects-faq-multiple-deep-link-objects-1.png["Sharing Saved Objects embeddable legacy URL conflict"] +Viewing details shows the user how to disable the alias and fix the problem using the +<>: ++ +image::images/sharing-saved-objects-faq-multiple-deep-link-objects-2.png["Sharing Saved Objects embeddable legacy URL conflict (showing details)"] - If the secondary object is resolved by an external service (such as the index pattern service), the service should simply make the full outcome available to consumers. diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index f9cbd35d72867..0e728a4dada24 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -94,6 +94,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a |Expression Metric plugin adds a metric renderer and function to the expression plugin. +|{kib-repo}blob/{branch}/src/plugins/chart_expressions/expression_metric/README.md[expressionMetricVis] +|Expression MetricVis plugin adds a metric renderer and function to the expression plugin. The renderer will display the metric chart. + + |{kib-repo}blob/{branch}/src/plugins/expression_repeat_image/README.md[expressionRepeatImage] |Expression Repeat Image plugin adds a repeatImage function to the expression plugin and an associated renderer. The renderer will display the given image in mutliple instances. diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index ab0f2d0ee5a17..c8ccdfeedb83f 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -143,6 +143,7 @@ readonly links: { readonly ruleChangeLog: string; readonly detectionsReq: string; readonly networkMap: string; + readonly troubleshootGaps: string; }; readonly query: { readonly eql: string; @@ -213,6 +214,10 @@ readonly links: { mappingRolesFieldRules: string; runAsPrivilege: string; }>; + readonly spaces: Readonly<{ + kibanaLegacyUrlAliases: string; + kibanaDisableLegacyUrlAliasesApi: string; + }>; readonly watcher: Record; readonly ccs: Record; readonly plugins: Record; @@ -230,6 +235,7 @@ readonly links: { datastreamsNamingScheme: string; upgradeElasticAgent: string; upgradeElasticAgent712lower: string; + learnMoreBlog: string; }>; readonly ecs: { readonly guide: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index f0fe058c403ed..04c2495cf3f1d 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Readonly<{
guide: string;
infrastructureThreshold: string;
logsThreshold: string;
metricsThreshold: string;
monitorStatus: string;
monitorUptime: string;
tlsCertificate: string;
uptimeDurationAnomaly: string;
}>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
readonly clients: {
readonly guide: string;
readonly goOverview: string;
readonly javaIndex: string;
readonly jsIntro: string;
readonly netGuide: string;
readonly perlGuide: string;
readonly phpGuide: string;
readonly pythonGuide: string;
readonly rubyOverview: string;
readonly rustGuide: string;
};
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Readonly<{
guide: string;
infrastructureThreshold: string;
logsThreshold: string;
metricsThreshold: string;
monitorStatus: string;
monitorUptime: string;
tlsCertificate: string;
uptimeDurationAnomaly: string;
}>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly spaces: Readonly<{
kibanaLegacyUrlAliases: string;
kibanaDisableLegacyUrlAliasesApi: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
readonly clients: {
readonly guide: string;
readonly goOverview: string;
readonly javaIndex: string;
readonly jsIntro: string;
readonly netGuide: string;
readonly perlGuide: string;
readonly phpGuide: string;
readonly pythonGuide: string;
readonly rubyOverview: string;
readonly rustGuide: string;
};
} | | diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index e565bda0dff47..fb96f68355330 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -40,57 +40,71 @@ Changing these settings may disable features of the APM App. [cols="2*<"] |=== -| `xpack.apm.enabled` +| `xpack.apm.enabled` {ess-icon} | deprecated:[7.16.0,"In 8.0 and later, this setting will no longer be supported."] Set to `false` to disable the APM app. Defaults to `true`. -| `xpack.apm.maxServiceEnvironments` +| `xpack.apm.maxServiceEnvironments` {ess-icon} | Maximum number of unique service environments recognized by the UI. Defaults to `100`. -| `xpack.apm.serviceMapFingerprintBucketSize` +| `xpack.apm.serviceMapFingerprintBucketSize` {ess-icon} | Maximum number of unique transaction combinations sampled for generating service map focused on a specific service. Defaults to `100`. -| `xpack.apm.serviceMapFingerprintGlobalBucketSize` +| `xpack.apm.serviceMapFingerprintGlobalBucketSize` {ess-icon} | Maximum number of unique transaction combinations sampled for generating the global service map. Defaults to `100`. +| `xpack.apm.serviceMapEnabled` {ess-icon} + | Set to `false` to disable service maps. Defaults to `true`. + +| `xpack.apm.serviceMapTraceIdBucketSize` {ess-icon} + | Maximum number of trace IDs sampled for generating service map focused on a specific service. Defaults to `65`. + +| `xpack.apm.serviceMapTraceIdGlobalBucketSize` {ess-icon} + | Maximum number of trace IDs sampled for generating the global service map. Defaults to `6`. + +| `xpack.apm.serviceMapMaxTracesPerRequest` {ess-icon} + | Maximum number of traces per request for generating the global service map. Defaults to `50`. + | `xpack.apm.ui.enabled` {ess-icon} | Set to `false` to hide the APM app from the main menu. Defaults to `true`. -| `xpack.apm.ui.transactionGroupBucketSize` +| `xpack.apm.ui.transactionGroupBucketSize` {ess-icon} | Number of top transaction groups displayed in the APM app. Defaults to `1000`. | `xpack.apm.ui.maxTraceItems` {ess-icon} | Maximum number of child items displayed when viewing trace details. Defaults to `1000`. -| `xpack.observability.annotations.index` +| `xpack.observability.annotations.index` {ess-icon} | Index name where Observability annotations are stored. Defaults to `observability-annotations`. -| `xpack.apm.searchAggregatedTransactions` +| `xpack.apm.searchAggregatedTransactions` {ess-icon} | experimental[] Enables Transaction histogram metrics. Defaults to `never` and aggregated transactions are not used. When set to `auto`, the UI will use metric indices over transaction indices for transactions if aggregated transactions are found. When set to `always`, additional configuration in APM Server is required. See {apm-server-ref-v}/transaction-metrics.html[Configure transaction metrics] for more information. -| `apm_oss.indexPattern` {ess-icon} - | The index pattern used for integrations with Machine Learning and Query Bar. - It must match all apm indices. Defaults to `apm-*`. +| `xpack.apm.metricsInterval` {ess-icon} + | Sets a `fixed_interval` for date histograms in metrics aggregations. Defaults to `30`. + +| `xpack.apm.agent.migrations.enabled` {ess-icon} + | Set to `false` to disable cloud APM migrations. Defaults to `true`. -| `apm_oss.errorIndices` {ess-icon} +| `xpack.apm.errorIndices` {ess-icon} | Matcher for all {apm-server-ref}/error-indices.html[error indices]. Defaults to `apm-*`. -| `apm_oss.onboardingIndices` +| `xpack.apm.onboardingIndices` {ess-icon} | Matcher for all onboarding indices. Defaults to `apm-*`. -| `apm_oss.spanIndices` {ess-icon} +| `xpack.apm.spanIndices` {ess-icon} | Matcher for all {apm-server-ref}/span-indices.html[span indices]. Defaults to `apm-*`. -| `apm_oss.transactionIndices` {ess-icon} +| `xpack.apm.transactionIndices` {ess-icon} | Matcher for all {apm-server-ref}/transaction-indices.html[transaction indices]. Defaults to `apm-*`. -| `apm_oss.metricsIndices` +| `xpack.apm.metricsIndices` {ess-icon} | Matcher for all {apm-server-ref}/metricset-indices.html[metrics indices]. Defaults to `apm-*`. -| `apm_oss.sourcemapIndices` +| `xpack.apm.sourcemapIndices` {ess-icon} | Matcher for all {apm-server-ref}/sourcemap-indices.html[source map indices]. Defaults to `apm-*`. |=== -// end::general-apm-settings[] +// end::general-apm-settings[] \ No newline at end of file diff --git a/package.json b/package.json index 187e1994e6167..0f778ce63231d 100644 --- a/package.json +++ b/package.json @@ -117,8 +117,8 @@ "@hapi/boom": "^9.1.4", "@hapi/cookie": "^11.0.2", "@hapi/h2o2": "^9.1.0", - "@hapi/hapi": "^20.2.0", - "@hapi/hoek": "^9.2.0", + "@hapi/hapi": "^20.2.1", + "@hapi/hoek": "^9.2.1", "@hapi/inert": "^6.0.4", "@hapi/wreck": "^17.1.0", "@kbn/ace": "link:bazel-bin/packages/kbn-ace", @@ -414,19 +414,19 @@ }, "devDependencies": { "@babel/cli": "^7.15.7", - "@babel/core": "^7.15.5", - "@babel/eslint-parser": "^7.15.7", + "@babel/core": "^7.15.8", + "@babel/eslint-parser": "^7.15.8", "@babel/eslint-plugin": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/parser": "^7.15.7", + "@babel/generator": "^7.15.8", + "@babel/parser": "^7.15.8", "@babel/plugin-proposal-class-properties": "^7.14.5", "@babel/plugin-proposal-export-namespace-from": "^7.14.5", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", "@babel/plugin-proposal-object-rest-spread": "^7.15.6", "@babel/plugin-proposal-optional-chaining": "^7.14.5", "@babel/plugin-proposal-private-methods": "^7.14.5", - "@babel/plugin-transform-runtime": "^7.15.0", - "@babel/preset-env": "^7.15.6", + "@babel/plugin-transform-runtime": "^7.15.8", + "@babel/preset-env": "^7.15.8", "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.15.0", "@babel/register": "^7.15.3", diff --git a/packages/kbn-cli-dev-mode/src/optimizer.test.ts b/packages/kbn-cli-dev-mode/src/optimizer.test.ts index ee8ea5f38ae84..e1763ab4c4756 100644 --- a/packages/kbn-cli-dev-mode/src/optimizer.test.ts +++ b/packages/kbn-cli-dev-mode/src/optimizer.test.ts @@ -18,9 +18,11 @@ import { Optimizer, Options } from './optimizer'; jest.mock('@kbn/optimizer'); const realOptimizer = jest.requireActual('@kbn/optimizer'); -const { runOptimizer, OptimizerConfig, logOptimizerState } = jest.requireMock('@kbn/optimizer'); +const { runOptimizer, OptimizerConfig, logOptimizerState, logOptimizerProgress } = + jest.requireMock('@kbn/optimizer'); logOptimizerState.mockImplementation(realOptimizer.logOptimizerState); +logOptimizerProgress.mockImplementation(realOptimizer.logOptimizerProgress); class MockOptimizerConfig {} diff --git a/packages/kbn-cli-dev-mode/src/optimizer.ts b/packages/kbn-cli-dev-mode/src/optimizer.ts index fab566829f7a6..3f7a6edc22314 100644 --- a/packages/kbn-cli-dev-mode/src/optimizer.ts +++ b/packages/kbn-cli-dev-mode/src/optimizer.ts @@ -18,7 +18,13 @@ import { } from '@kbn/dev-utils'; import * as Rx from 'rxjs'; import { ignoreElements } from 'rxjs/operators'; -import { runOptimizer, OptimizerConfig, logOptimizerState, OptimizerUpdate } from '@kbn/optimizer'; +import { + runOptimizer, + OptimizerConfig, + logOptimizerState, + logOptimizerProgress, + OptimizerUpdate, +} from '@kbn/optimizer'; export interface Options { enabled: boolean; @@ -111,6 +117,7 @@ export class Optimizer { subscriber.add( runOptimizer(config) .pipe( + logOptimizerProgress(log), logOptimizerState(log, config), tap(({ state }) => { this.phase$.next(state.phase); diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts index 45d31c1eefad9..fe48ce99e6857 100644 --- a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts +++ b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts @@ -13,6 +13,8 @@ import Path from 'path'; import crypto from 'crypto'; import execa from 'execa'; import Axios from 'axios'; +// @ts-expect-error not "public", but necessary to prevent Jest shimming from breaking things +import httpAdapter from 'axios/lib/adapters/http'; import { ToolingLog } from '../tooling_log'; import { parseConfig, Config } from './ci_stats_config'; @@ -225,6 +227,7 @@ export class CiStatsReporter { baseURL: BASE_URL, headers, data: body, + adapter: httpAdapter, }); return true; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 80cd01e05b870..acef661d93bb0 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -80,7 +80,6 @@ pageLoadAssetSize: usageCollection: 39762 visDefaultEditor: 50178 visTypeMarkdown: 30896 - visTypeMetric: 42790 visTypeTable: 94934 visTypeTagcloud: 37575 visTypeTimelion: 68883 @@ -115,4 +114,6 @@ pageLoadAssetSize: expressions: 239290 securitySolution: 231753 customIntegrations: 28810 + expressionMetricVis: 23121 + visTypeMetric: 23332 dataViews: 42000 diff --git a/packages/kbn-optimizer/src/cli.ts b/packages/kbn-optimizer/src/cli.ts index d5b9996dfb2cd..7f0c39ccd0e55 100644 --- a/packages/kbn-optimizer/src/cli.ts +++ b/packages/kbn-optimizer/src/cli.ts @@ -13,6 +13,7 @@ import { lastValueFrom } from '@kbn/std'; import { run, createFlagError, Flags } from '@kbn/dev-utils'; import { logOptimizerState } from './log_optimizer_state'; +import { logOptimizerProgress } from './log_optimizer_progress'; import { OptimizerConfig } from './optimizer'; import { runOptimizer } from './run_optimizer'; import { validateLimitsForAllBundles, updateBundleLimits } from './limits'; @@ -97,6 +98,11 @@ export function runKbnOptimizerCli(options: { defaultLimitsPath: string }) { throw createFlagError('expected --report-stats to have no value'); } + const logProgress = flags.progress ?? false; + if (typeof logProgress !== 'boolean') { + throw createFlagError('expected --progress to have no value'); + } + const filter = typeof flags.filter === 'string' ? [flags.filter] : flags.filter; if (!Array.isArray(filter) || !filter.every((f) => typeof f === 'string')) { throw createFlagError('expected --filter to be one or more strings'); @@ -144,7 +150,11 @@ export function runKbnOptimizerCli(options: { defaultLimitsPath: string }) { const update$ = runOptimizer(config); await lastValueFrom( - update$.pipe(logOptimizerState(log, config), reportOptimizerTimings(log, config)) + update$.pipe( + logProgress ? logOptimizerProgress(log) : (x) => x, + logOptimizerState(log, config), + reportOptimizerTimings(log, config) + ) ); if (updateLimits) { @@ -169,6 +179,7 @@ export function runKbnOptimizerCli(options: { defaultLimitsPath: string }) { 'inspect-workers', 'validate-limits', 'update-limits', + 'progress', ], string: ['workers', 'scan-dir', 'filter', 'limits'], default: { @@ -176,12 +187,14 @@ export function runKbnOptimizerCli(options: { defaultLimitsPath: string }) { examples: true, cache: true, 'inspect-workers': true, + progress: true, filter: [], focus: [], }, help: ` --watch run the optimizer in watch mode --workers max number of workers to use + --no-progress disable logging of progress information --oss only build oss plugins --profile profile the webpack builds and write stats.json files to build outputs --no-core disable generating the core bundle diff --git a/packages/kbn-optimizer/src/index.ts b/packages/kbn-optimizer/src/index.ts index a5838a8a0fac8..d5e810d584d29 100644 --- a/packages/kbn-optimizer/src/index.ts +++ b/packages/kbn-optimizer/src/index.ts @@ -9,6 +9,7 @@ export { OptimizerConfig } from './optimizer'; export * from './run_optimizer'; export * from './log_optimizer_state'; +export * from './log_optimizer_progress'; export * from './node'; export * from './limits'; export * from './cli'; diff --git a/packages/kbn-optimizer/src/log_optimizer_progress.ts b/packages/kbn-optimizer/src/log_optimizer_progress.ts new file mode 100644 index 0000000000000..d07c9dc6eff32 --- /dev/null +++ b/packages/kbn-optimizer/src/log_optimizer_progress.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { ToolingLog } from '@kbn/dev-utils'; +import * as Rx from 'rxjs'; +import { tap } from 'rxjs/operators'; + +import { OptimizerUpdate } from './run_optimizer'; + +const PROGRESS_REPORT_INTERVAL = 10_000; + +export function logOptimizerProgress( + log: ToolingLog +): Rx.MonoTypeOperatorFunction { + return (update$) => + new Rx.Observable((subscriber) => { + const allBundleIds = new Set(); + const completeBundles = new Set(); + let loggedCompletion = new Set(); + + // catalog bundle ids and which have completed at least once, forward + // updates to next subscriber + subscriber.add( + update$ + .pipe( + tap(({ state }) => { + for (const { bundleId, type } of state.compilerStates) { + allBundleIds.add(bundleId); + if (type !== 'running') { + completeBundles.add(bundleId); + } + } + }), + tap(subscriber) + ) + .subscribe() + ); + + // on interval check to see if at least 3 new bundles have completed at + // least one build and log about our progress if so + subscriber.add( + Rx.interval(PROGRESS_REPORT_INTERVAL).subscribe( + () => { + if (completeBundles.size - loggedCompletion.size < 3) { + return; + } + + log.info( + `[${completeBundles.size}/${allBundleIds.size}] initial bundle builds complete` + ); + loggedCompletion = new Set(completeBundles); + }, + (error) => subscriber.error(error) + ) + ); + }); +} diff --git a/packages/kbn-optimizer/src/log_optimizer_state.ts b/packages/kbn-optimizer/src/log_optimizer_state.ts index 61f6406255a8c..517e3bbfa5133 100644 --- a/packages/kbn-optimizer/src/log_optimizer_state.ts +++ b/packages/kbn-optimizer/src/log_optimizer_state.ts @@ -82,14 +82,11 @@ export function logOptimizerState(log: ToolingLog, config: OptimizerConfig) { continue; } + bundleStates.set(id, type); + if (type === 'running') { bundlesThatWereBuilt.add(id); } - - bundleStates.set(id, type); - log.debug( - `[${id}] state = "${type}"${type !== 'running' ? ` after ${state.durSec} sec` : ''}` - ); } if (state.phase === 'running' || state.phase === 'initializing') { diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index cab1f6d916f02..f395636379141 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -8965,6 +8965,8 @@ var _execa = _interopRequireDefault(__webpack_require__(134)); var _axios = _interopRequireDefault(__webpack_require__(177)); +var _http = _interopRequireDefault(__webpack_require__(199)); + var _ci_stats_config = __webpack_require__(218); /* @@ -8974,6 +8976,7 @@ var _ci_stats_config = __webpack_require__(218); * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +// @ts-expect-error not "public", but necessary to prevent Jest shimming from breaking things const BASE_URL = 'https://ci-stats.kibana.dev'; class CiStatsReporter { @@ -9173,7 +9176,8 @@ class CiStatsReporter { url: path, baseURL: BASE_URL, headers, - data: body + data: body, + adapter: _http.default }); return true; } catch (error) { diff --git a/packages/kbn-storybook/src/lib/run_storybook_cli.ts b/packages/kbn-storybook/src/lib/run_storybook_cli.ts index 24a3e4511f7be..93197a1f2b318 100644 --- a/packages/kbn-storybook/src/lib/run_storybook_cli.ts +++ b/packages/kbn-storybook/src/lib/run_storybook_cli.ts @@ -36,7 +36,11 @@ export function runStorybookCli({ configDir, name }: { configDir: string; name: async ({ flags, log }) => { log.debug('Global config:\n', constants); - const staticDir = [UiSharedDepsNpm.distDir, UiSharedDepsSrc.distDir]; + const staticDir = [ + UiSharedDepsNpm.distDir, + UiSharedDepsSrc.distDir, + 'src/plugins/kibana_react/public/assets:plugins/kibanaReact/assets', + ]; const config: Record = { configDir, mode: flags.site ? 'static' : 'dev', diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 9a9b7aabc4dec..01108298adc99 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -25,6 +25,7 @@ export class DocLinksService { const FLEET_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/fleet/${DOC_LINK_VERSION}/`; const PLUGIN_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`; const APM_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/apm/`; + const SECURITY_SOLUTION_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/`; return deepFreeze({ DOC_LINK_VERSION, @@ -223,13 +224,14 @@ export class DocLinksService { typesRemoval: `${ELASTICSEARCH_DOCS}removal-of-types.html`, }, siem: { - guide: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, - gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, - privileges: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/sec-requirements.html`, - ml: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/machine-learning.html`, - ruleChangeLog: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/prebuilt-rules-changelog.html`, - detectionsReq: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/detections-permissions-section.html`, - networkMap: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/conf-map-ui.html`, + guide: `${SECURITY_SOLUTION_DOCS}index.html`, + gettingStarted: `${SECURITY_SOLUTION_DOCS}index.html`, + privileges: `${SECURITY_SOLUTION_DOCS}sec-requirements.html`, + ml: `${SECURITY_SOLUTION_DOCS}machine-learning.html`, + ruleChangeLog: `${SECURITY_SOLUTION_DOCS}prebuilt-rules-changelog.html`, + detectionsReq: `${SECURITY_SOLUTION_DOCS}detections-permissions-section.html`, + networkMap: `${SECURITY_SOLUTION_DOCS}conf-map-ui.html`, + troubleshootGaps: `${SECURITY_SOLUTION_DOCS}alerts-ui-monitor.html#troubleshoot-gaps`, }, query: { eql: `${ELASTICSEARCH_DOCS}eql.html`, @@ -356,6 +358,10 @@ export class DocLinksService { mappingRolesFieldRules: `${ELASTICSEARCH_DOCS}role-mapping-resources.html#mapping-roles-rule-field`, runAsPrivilege: `${ELASTICSEARCH_DOCS}security-privileges.html#_run_as_privilege`, }, + spaces: { + kibanaLegacyUrlAliases: `${KIBANA_DOCS}legacy-url-aliases.html`, + kibanaDisableLegacyUrlAliasesApi: `${KIBANA_DOCS}spaces-api-disable-legacy-url-aliases.html`, + }, watcher: { jiraAction: `${ELASTICSEARCH_DOCS}actions-jira.html`, pagerDutyAction: `${ELASTICSEARCH_DOCS}actions-pagerduty.html`, @@ -470,6 +476,7 @@ export class DocLinksService { datastreamsNamingScheme: `${FLEET_DOCS}data-streams.html#data-streams-naming-scheme`, upgradeElasticAgent: `${FLEET_DOCS}upgrade-elastic-agent.html`, upgradeElasticAgent712lower: `${FLEET_DOCS}upgrade-elastic-agent.html#upgrade-7.12-lower`, + learnMoreBlog: `${ELASTIC_WEBSITE_URL}blog/elastic-agent-and-fleet-make-it-easier-to-integrate-your-systems-with-elastic`, }, ecs: { guide: `${ELASTIC_WEBSITE_URL}guide/en/ecs/current/index.html`, @@ -632,6 +639,7 @@ export interface DocLinksStart { readonly ruleChangeLog: string; readonly detectionsReq: string; readonly networkMap: string; + readonly troubleshootGaps: string; }; readonly query: { readonly eql: string; @@ -702,6 +710,10 @@ export interface DocLinksStart { mappingRolesFieldRules: string; runAsPrivilege: string; }>; + readonly spaces: Readonly<{ + kibanaLegacyUrlAliases: string; + kibanaDisableLegacyUrlAliasesApi: string; + }>; readonly watcher: Record; readonly ccs: Record; readonly plugins: Record; @@ -719,6 +731,7 @@ export interface DocLinksStart { datastreamsNamingScheme: string; upgradeElasticAgent: string; upgradeElasticAgent712lower: string; + learnMoreBlog: string; }>; readonly ecs: { readonly guide: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 7871558574074..45b7e3bdc02b5 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -612,6 +612,7 @@ export interface DocLinksStart { readonly ruleChangeLog: string; readonly detectionsReq: string; readonly networkMap: string; + readonly troubleshootGaps: string; }; readonly query: { readonly eql: string; @@ -682,6 +683,10 @@ export interface DocLinksStart { mappingRolesFieldRules: string; runAsPrivilege: string; }>; + readonly spaces: Readonly<{ + kibanaLegacyUrlAliases: string; + kibanaDisableLegacyUrlAliasesApi: string; + }>; readonly watcher: Record; readonly ccs: Record; readonly plugins: Record; @@ -699,6 +704,7 @@ export interface DocLinksStart { datastreamsNamingScheme: string; upgradeElasticAgent: string; upgradeElasticAgent712lower: string; + learnMoreBlog: string; }>; readonly ecs: { readonly guide: string; diff --git a/src/core/server/status/get_summary_status.test.ts b/src/core/server/status/get_summary_status.test.ts index 33b2e6f7913a1..2c91aa8c7b16a 100644 --- a/src/core/server/status/get_summary_status.test.ts +++ b/src/core/server/status/get_summary_status.test.ts @@ -81,93 +81,86 @@ describe('getSummaryStatus', () => { }); describe('summary', () => { - describe('when a single service is at highest level', () => { - it('returns all information about that single service', () => { - expect( - getSummaryStatus( - Object.entries({ - s1: degraded, - s2: { - level: ServiceStatusLevels.unavailable, - summary: 'Lorem ipsum', - meta: { - custom: { data: 'here' }, - }, + it('returns correct summary when a single service is affected', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: degraded, + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + meta: { + custom: { data: 'here' }, }, - }) - ) - ).toEqual({ - level: ServiceStatusLevels.unavailable, - summary: '[s2]: Lorem ipsum', - detail: 'See the status page for more information', - meta: { - affectedServices: ['s2'], - }, - }); + }, + }) + ) + ).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: '1 service is unavailable: s2', + detail: 'See the status page for more information', + meta: { + affectedServices: ['s2'], + }, }); + }); - it('allows the single service to override the detail and documentationUrl fields', () => { - expect( - getSummaryStatus( - Object.entries({ - s1: degraded, - s2: { - level: ServiceStatusLevels.unavailable, - summary: 'Lorem ipsum', - detail: 'Vivamus pulvinar sem ac luctus ultrices.', - documentationUrl: 'http://helpmenow.com/problem1', - meta: { - custom: { data: 'here' }, - }, + it('returns correct summary when multiple services are affected', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: degraded, + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + detail: 'Vivamus pulvinar sem ac luctus ultrices.', + documentationUrl: 'http://helpmenow.com/problem1', + meta: { + custom: { data: 'here' }, + }, + }, + s3: { + level: ServiceStatusLevels.unavailable, + summary: 'Proin mattis', + detail: 'Nunc quis nulla at mi lobortis pretium.', + documentationUrl: 'http://helpmenow.com/problem2', + meta: { + other: { data: 'over there' }, }, - }) - ) - ).toEqual({ - level: ServiceStatusLevels.unavailable, - summary: '[s2]: Lorem ipsum', - detail: 'Vivamus pulvinar sem ac luctus ultrices.', - documentationUrl: 'http://helpmenow.com/problem1', - meta: { - affectedServices: ['s2'], - }, - }); + }, + }) + ) + ).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: '2 services are unavailable: s2, s3', + detail: 'See the status page for more information', + meta: { + affectedServices: ['s2', 's3'], + }, }); }); - describe('when multiple services is at highest level', () => { - it('returns aggregated information about the affected services', () => { - expect( - getSummaryStatus( - Object.entries({ - s1: degraded, - s2: { - level: ServiceStatusLevels.unavailable, - summary: 'Lorem ipsum', - detail: 'Vivamus pulvinar sem ac luctus ultrices.', - documentationUrl: 'http://helpmenow.com/problem1', - meta: { - custom: { data: 'here' }, - }, - }, - s3: { - level: ServiceStatusLevels.unavailable, - summary: 'Proin mattis', - detail: 'Nunc quis nulla at mi lobortis pretium.', - documentationUrl: 'http://helpmenow.com/problem2', - meta: { - other: { data: 'over there' }, - }, - }, - }) - ) - ).toEqual({ - level: ServiceStatusLevels.unavailable, - summary: '[2] services are unavailable', - detail: 'See the status page for more information', - meta: { - affectedServices: ['s2', 's3'], - }, - }); + it('returns correct summary more than `maxServices` services are affected', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: degraded, + s2: available, + s3: degraded, + s4: degraded, + s5: degraded, + s6: available, + s7: degraded, + }), + { maxServices: 3 } + ) + ).toEqual({ + level: ServiceStatusLevels.degraded, + summary: '5 services are degraded: s1, s3, s4 and 2 other(s)', + detail: 'See the status page for more information', + meta: { + affectedServices: ['s1', 's3', 's4', 's5', 's7'], + }, }); }); }); diff --git a/src/core/server/status/get_summary_status.ts b/src/core/server/status/get_summary_status.ts index 9124023148dd1..1dc939ce3f80c 100644 --- a/src/core/server/status/get_summary_status.ts +++ b/src/core/server/status/get_summary_status.ts @@ -10,11 +10,13 @@ import { ServiceStatus, ServiceStatusLevels, ServiceStatusLevel } from './types' /** * Returns a single {@link ServiceStatus} that summarizes the most severe status level from a group of statuses. - * @param statuses */ export const getSummaryStatus = ( statuses: Array<[string, ServiceStatus]>, - { allAvailableSummary = `All services are available` }: { allAvailableSummary?: string } = {} + { + allAvailableSummary = `All services are available`, + maxServices = 3, + }: { allAvailableSummary?: string; maxServices?: number } = {} ): ServiceStatus => { const { highestLevel, highestStatuses } = highestLevelSummary(statuses); @@ -23,30 +25,38 @@ export const getSummaryStatus = ( level: ServiceStatusLevels.available, summary: allAvailableSummary, }; - } else if (highestStatuses.length === 1) { - const [serviceName, status] = highestStatuses[0]! as [string, ServiceStatus]; - return { - ...status, - summary: `[${serviceName}]: ${status.summary!}`, - // TODO: include URL to status page - detail: status.detail ?? `See the status page for more information`, - meta: { - affectedServices: [serviceName], - }, - }; } else { + const affectedServices = highestStatuses.map(([serviceName]) => serviceName); return { level: highestLevel, - summary: `[${highestStatuses.length}] services are ${highestLevel.toString()}`, + summary: getSummaryContent(affectedServices, highestLevel, maxServices), // TODO: include URL to status page detail: `See the status page for more information`, meta: { - affectedServices: highestStatuses.map(([serviceName]) => serviceName), + affectedServices, }, }; } }; +const getSummaryContent = ( + affectedServices: string[], + statusLevel: ServiceStatusLevel, + maxServices: number +): string => { + const serviceCount = affectedServices.length; + if (serviceCount === 1) { + return `1 service is ${statusLevel.toString()}: ${affectedServices[0]}`; + } else if (serviceCount > maxServices) { + const exceedingCount = serviceCount - maxServices; + return `${serviceCount} services are ${statusLevel.toString()}: ${affectedServices + .slice(0, maxServices) + .join(', ')} and ${exceedingCount} other(s)`; + } else { + return `${serviceCount} services are ${statusLevel.toString()}: ${affectedServices.join(', ')}`; + } +}; + type StatusPair = [string, ServiceStatus]; const highestLevelSummary = ( diff --git a/src/core/server/status/plugins_status.test.ts b/src/core/server/status/plugins_status.test.ts index b7c0733de728e..0befbf63bd186 100644 --- a/src/core/server/status/plugins_status.test.ts +++ b/src/core/server/status/plugins_status.test.ts @@ -73,7 +73,7 @@ describe('PluginStatusService', () => { }); expect(await serviceDegraded.getDerivedStatus$('a').pipe(first()).toPromise()).toEqual({ level: ServiceStatusLevels.degraded, - summary: '[savedObjects]: savedObjects degraded', + summary: '1 service is degraded: savedObjects', detail: 'See the status page for more information', meta: expect.any(Object), }); @@ -84,7 +84,7 @@ describe('PluginStatusService', () => { }); expect(await serviceCritical.getDerivedStatus$('a').pipe(first()).toPromise()).toEqual({ level: ServiceStatusLevels.critical, - summary: '[elasticsearch]: elasticsearch critical', + summary: '1 service is critical: elasticsearch', detail: 'See the status page for more information', meta: expect.any(Object), }); @@ -95,7 +95,7 @@ describe('PluginStatusService', () => { service.set('a', of({ level: ServiceStatusLevels.degraded, summary: 'a is degraded' })); expect(await service.getDerivedStatus$('b').pipe(first()).toPromise()).toEqual({ level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '2 services are degraded: savedObjects, a', detail: 'See the status page for more information', meta: expect.any(Object), }); @@ -106,7 +106,7 @@ describe('PluginStatusService', () => { service.set('a', of({ level: ServiceStatusLevels.unavailable, summary: 'a is not working' })); expect(await service.getDerivedStatus$('b').pipe(first()).toPromise()).toEqual({ level: ServiceStatusLevels.unavailable, - summary: '[a]: a is not working', + summary: '1 service is unavailable: a', detail: 'See the status page for more information', meta: expect.any(Object), }); @@ -120,7 +120,7 @@ describe('PluginStatusService', () => { service.set('a', of({ level: ServiceStatusLevels.unavailable, summary: 'a is not working' })); expect(await service.getDerivedStatus$('b').pipe(first()).toPromise()).toEqual({ level: ServiceStatusLevels.critical, - summary: '[elasticsearch]: elasticsearch critical', + summary: '1 service is critical: elasticsearch', detail: 'See the status page for more information', meta: expect.any(Object), }); @@ -132,7 +132,7 @@ describe('PluginStatusService', () => { service.set('b', of({ level: ServiceStatusLevels.unavailable, summary: 'b is not working' })); expect(await service.getDerivedStatus$('c').pipe(first()).toPromise()).toEqual({ level: ServiceStatusLevels.unavailable, - summary: '[b]: b is not working', + summary: '1 service is unavailable: b', detail: 'See the status page for more information', meta: expect.any(Object), }); @@ -166,19 +166,19 @@ describe('PluginStatusService', () => { expect(await serviceDegraded.getAll$().pipe(first()).toPromise()).toEqual({ a: { level: ServiceStatusLevels.degraded, - summary: '[savedObjects]: savedObjects degraded', + summary: '1 service is degraded: savedObjects', detail: 'See the status page for more information', meta: expect.any(Object), }, b: { level: ServiceStatusLevels.degraded, - summary: '[savedObjects]: savedObjects degraded', + summary: '1 service is degraded: savedObjects', detail: 'See the status page for more information', meta: expect.any(Object), }, c: { level: ServiceStatusLevels.degraded, - summary: '[savedObjects]: savedObjects degraded', + summary: '1 service is degraded: savedObjects', detail: 'See the status page for more information', meta: expect.any(Object), }, @@ -191,19 +191,19 @@ describe('PluginStatusService', () => { expect(await serviceCritical.getAll$().pipe(first()).toPromise()).toEqual({ a: { level: ServiceStatusLevels.critical, - summary: '[elasticsearch]: elasticsearch critical', + summary: '1 service is critical: elasticsearch', detail: 'See the status page for more information', meta: expect.any(Object), }, b: { level: ServiceStatusLevels.critical, - summary: '[elasticsearch]: elasticsearch critical', + summary: '1 service is critical: elasticsearch', detail: 'See the status page for more information', meta: expect.any(Object), }, c: { level: ServiceStatusLevels.critical, - summary: '[elasticsearch]: elasticsearch critical', + summary: '1 service is critical: elasticsearch', detail: 'See the status page for more information', meta: expect.any(Object), }, @@ -218,13 +218,13 @@ describe('PluginStatusService', () => { a: { level: ServiceStatusLevels.available, summary: 'a status' }, // a is available depsite savedObjects being degraded b: { level: ServiceStatusLevels.degraded, - summary: '[savedObjects]: savedObjects degraded', + summary: '1 service is degraded: savedObjects', detail: 'See the status page for more information', meta: expect.any(Object), }, c: { level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '2 services are degraded: savedObjects, b', detail: 'See the status page for more information', meta: expect.any(Object), }, @@ -298,7 +298,7 @@ describe('PluginStatusService', () => { a: { level: ServiceStatusLevels.unavailable, summary: 'Status check timed out after 30s' }, b: { level: ServiceStatusLevels.unavailable, - summary: '[a]: Status check timed out after 30s', + summary: '1 service is unavailable: a', detail: 'See the status page for more information', meta: { affectedServices: ['a'], @@ -341,7 +341,7 @@ describe('PluginStatusService', () => { a: { level: ServiceStatusLevels.available, summary: 'a status' }, // a is available depsite savedObjects being degraded b: { level: ServiceStatusLevels.degraded, - summary: '[savedObjects]: savedObjects degraded', + summary: '1 service is degraded: savedObjects', detail: 'See the status page for more information', meta: expect.any(Object), }, diff --git a/src/core/server/status/status_service.test.ts b/src/core/server/status/status_service.test.ts index 255ed821bc2fe..dfd0ff9a7e103 100644 --- a/src/core/server/status/status_service.test.ts +++ b/src/core/server/status/status_service.test.ts @@ -188,7 +188,7 @@ describe('StatusService', () => { ); expect(await setup.overall$.pipe(first()).toPromise()).toMatchObject({ level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '2 services are degraded: elasticsearch, savedObjects', }); }); @@ -208,15 +208,15 @@ describe('StatusService', () => { const subResult3 = await setup.overall$.pipe(first()).toPromise(); expect(subResult1).toMatchObject({ level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '2 services are degraded: elasticsearch, savedObjects', }); expect(subResult2).toMatchObject({ level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '2 services are degraded: elasticsearch, savedObjects', }); expect(subResult3).toMatchObject({ level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '2 services are degraded: elasticsearch, savedObjects', }); }); @@ -265,7 +265,7 @@ describe('StatusService', () => { "savedObjects", ], }, - "summary": "[savedObjects]: This is degraded!", + "summary": "1 service is degraded: savedObjects", }, Object { "level": available, @@ -315,7 +315,7 @@ describe('StatusService', () => { "savedObjects", ], }, - "summary": "[savedObjects]: This is degraded!", + "summary": "1 service is degraded: savedObjects", }, Object { "level": available, @@ -340,7 +340,7 @@ describe('StatusService', () => { ); expect(await setup.coreOverall$.pipe(first()).toPromise()).toMatchObject({ level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '2 services are degraded: elasticsearch, savedObjects', }); }); @@ -357,7 +357,7 @@ describe('StatusService', () => { ); expect(await setup.coreOverall$.pipe(first()).toPromise()).toMatchObject({ level: ServiceStatusLevels.critical, - summary: '[savedObjects]: This is critical!', + summary: '1 service is critical: savedObjects', }); }); @@ -379,15 +379,15 @@ describe('StatusService', () => { expect(subResult1).toMatchObject({ level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '2 services are degraded: elasticsearch, savedObjects', }); expect(subResult2).toMatchObject({ level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '2 services are degraded: elasticsearch, savedObjects', }); expect(subResult3).toMatchObject({ level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '2 services are degraded: elasticsearch, savedObjects', }); }); @@ -436,7 +436,7 @@ describe('StatusService', () => { "savedObjects", ], }, - "summary": "[savedObjects]: This is degraded!", + "summary": "1 service is degraded: savedObjects", }, Object { "level": available, @@ -486,7 +486,7 @@ describe('StatusService', () => { "savedObjects", ], }, - "summary": "[savedObjects]: This is degraded!", + "summary": "1 service is degraded: savedObjects", }, Object { "level": available, diff --git a/src/dev/build/tasks/os_packages/create_os_package_kibana_yml.ts b/src/dev/build/tasks/os_packages/create_os_package_kibana_yml.ts index be21bc4930591..ebd62f83798d7 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_kibana_yml.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_kibana_yml.ts @@ -23,7 +23,7 @@ export async function createOSPackageKibanaYML(config: Config, build: Build) { [ [/#pid.file:.*/g, 'pid.file: /run/kibana/kibana.pid'], [ - /#logging.dest:.*/g, + /#logging.appenders.default:.*kibana\.log\n/gs, dump({ logging: { appenders: { diff --git a/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js b/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js index fa82641e142d0..05af7c2a154a4 100644 --- a/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js +++ b/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js @@ -26,13 +26,13 @@ describe(`enumeratePatterns`, () => { ) ).toBe(true); }); - it(`should resolve src/plugins/charts/public/static/color_maps/color_maps.ts to kibana-app`, () => { + it(`should resolve src/plugins/charts/common/static/color_maps/color_maps.ts to kibana-app`, () => { const actual = enumeratePatterns(REPO_ROOT)(log)( - new Map([['src/plugins/charts/public/static/color_maps', ['kibana-app']]]) + new Map([['src/plugins/charts/common/static/color_maps', ['kibana-app']]]) ); expect(actual[0][0]).toBe( - 'src/plugins/charts/public/static/color_maps/color_maps.ts kibana-app' + 'src/plugins/charts/common/static/color_maps/color_maps.ts kibana-app' ); }); it(`should resolve x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts to kibana-security`, () => { diff --git a/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js b/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js index 60a68253d2c4e..5e33f2ba548aa 100644 --- a/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js +++ b/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js @@ -98,7 +98,7 @@ describe(`Transform fns`, () => { }); }); - describe(`with a coveredFilePath of src/plugins/charts/public/static/color_maps/color_maps.ts`, () => { + describe(`with a coveredFilePath of src/plugins/charts/common/static/color_maps/color_maps.ts`, () => { const expected = 'kibana-reporting'; it(`should resolve to ${expected}`, async () => { const actual = await teamAssignment(teamAssignmentsPathMOCK)(log)(obj); @@ -110,25 +110,25 @@ describe(`Transform fns`, () => { describe(`last fn`, () => { describe(`applied to n results`, () => { it(`should pick the last one`, () => { - const nteams = `src/plugins/charts/public/static/color_maps/color_maps.ts kibana-app -src/plugins/charts/public/static/color_maps/color_maps.ts kibana-app-arch`; + const nteams = `src/plugins/charts/common/static/color_maps/color_maps.ts kibana-app +src/plugins/charts/common/static/color_maps/color_maps.ts kibana-app-arch`; const actual = last(nteams); expect(actual).toBe( - 'src/plugins/charts/public/static/color_maps/color_maps.ts kibana-app-arch' + 'src/plugins/charts/common/static/color_maps/color_maps.ts kibana-app-arch' ); }); }); describe(`applied to 1 result`, () => { it(`should pick that 1 result`, () => { const nteams = - 'src/plugins/charts/public/static/color_maps/color_maps.ts kibana-app-arch'; + 'src/plugins/charts/common/static/color_maps/color_maps.ts kibana-app-arch'; const actual = last(nteams); expect(actual).toBe( - 'src/plugins/charts/public/static/color_maps/color_maps.ts kibana-app-arch' + 'src/plugins/charts/common/static/color_maps/color_maps.ts kibana-app-arch' ); }); }); diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index c04f0d4f9320f..ee09c94477af4 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -25,6 +25,7 @@ export const storybookAliases = { expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook', expression_shape: 'src/plugins/expression_shape/.storybook', expression_tagcloud: 'src/plugins/chart_expressions/expression_tagcloud/.storybook', + expression_metric_vis: 'src/plugins/chart_expressions/expression_metric/.storybook', fleet: 'x-pack/plugins/fleet/storybook', infra: 'x-pack/plugins/infra/.storybook', security_solution: 'x-pack/plugins/security_solution/.storybook', diff --git a/src/dev/typescript/build_ts_refs.ts b/src/dev/typescript/build_ts_refs.ts index db51ff999ccc3..aaa8c0d12fa4d 100644 --- a/src/dev/typescript/build_ts_refs.ts +++ b/src/dev/typescript/build_ts_refs.ts @@ -11,17 +11,20 @@ import Path from 'path'; import { ToolingLog, REPO_ROOT, ProcRunner } from '@kbn/dev-utils'; import { ROOT_REFS_CONFIG_PATH } from './root_refs_config'; +import { Project } from './project'; -export async function buildAllTsRefs({ +export async function buildTsRefs({ log, procRunner, verbose, + project, }: { log: ToolingLog; procRunner: ProcRunner; verbose?: boolean; + project?: Project; }): Promise<{ failed: boolean }> { - const relative = Path.relative(REPO_ROOT, ROOT_REFS_CONFIG_PATH); + const relative = Path.relative(REPO_ROOT, project ? project.tsConfigPath : ROOT_REFS_CONFIG_PATH); log.info(`Building TypeScript projects refs for ${relative}...`); try { diff --git a/src/dev/typescript/build_ts_refs_cli.ts b/src/dev/typescript/build_ts_refs_cli.ts index a1f1d1ac7028b..c68424c2a98f7 100644 --- a/src/dev/typescript/build_ts_refs_cli.ts +++ b/src/dev/typescript/build_ts_refs_cli.ts @@ -8,11 +8,11 @@ import Path from 'path'; -import { run, REPO_ROOT } from '@kbn/dev-utils'; +import { run, REPO_ROOT, createFlagError } from '@kbn/dev-utils'; import del from 'del'; import { RefOutputCache } from './ref_output_cache'; -import { buildAllTsRefs } from './build_ts_refs'; +import { buildTsRefs } from './build_ts_refs'; import { updateRootRefsConfig, ROOT_REFS_CONFIG_PATH } from './root_refs_config'; import { Project } from './project'; import { PROJECT_CACHE } from './projects'; @@ -41,25 +41,35 @@ export async function runBuildRefsCli() { return; } + const projectFilter = flags.project; + if (projectFilter && typeof projectFilter !== 'string') { + throw createFlagError('expected --project to be a string'); + } + // if the tsconfig.refs.json file is not self-managed then make sure it has // a reference to every composite project in the repo await updateRootRefsConfig(log); - // load all the projects referenced from the root refs config deeply, so we know all - // the ts projects we are going to be cleaning or populating with caches - const projects = Project.load( - ROOT_REFS_CONFIG_PATH, + const rootProject = Project.load( + projectFilter ? projectFilter : ROOT_REFS_CONFIG_PATH, {}, { skipConfigValidation: true, } - ).getProjectsDeep(PROJECT_CACHE); + ); + // load all the projects referenced from the root project deeply, so we know all + // the ts projects we are going to be cleaning or populating with caches + const projects = rootProject.getProjectsDeep(PROJECT_CACHE); const cacheEnabled = process.env.BUILD_TS_REFS_CACHE_ENABLE !== 'false' && !!flags.cache; const doCapture = process.env.BUILD_TS_REFS_CACHE_CAPTURE === 'true'; const doClean = !!flags.clean || doCapture; const doInitCache = cacheEnabled && !doCapture; + if (doCapture && projectFilter) { + throw createFlagError('--project can not be combined with cache capture'); + } + statsMeta.set('buildTsRefsEnabled', enabled); statsMeta.set('buildTsRefsCacheEnabled', cacheEnabled); statsMeta.set('buildTsRefsDoCapture', doCapture); @@ -87,7 +97,12 @@ export async function runBuildRefsCli() { } try { - await buildAllTsRefs({ log, procRunner, verbose: !!flags.verbose }); + await buildTsRefs({ + log, + procRunner, + verbose: !!flags.verbose, + project: rootProject, + }); log.success('ts refs build successfully'); } catch (error) { const typeFailure = isTypeFailure(error); @@ -110,13 +125,15 @@ export async function runBuildRefsCli() { } }, { - description: 'Build TypeScript projects', + description: 'Build TypeScript project references', flags: { boolean: ['clean', 'force', 'cache', 'ignore-type-failures'], + string: ['project'], default: { cache: true, }, help: ` + --project Only build the TS Refs for a specific project --force Run the build even if the BUILD_TS_REFS_DISABLE is set to "true" --clean Delete outDirs for each ts project before building --no-cache Disable fetching/extracting outDir caches based on the mergeBase with upstream diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts index 6a28631322857..472b9c04757ca 100644 --- a/src/dev/typescript/run_type_check_cli.ts +++ b/src/dev/typescript/run_type_check_cli.ts @@ -16,7 +16,7 @@ import { run, createFailError } from '@kbn/dev-utils'; import { lastValueFrom } from '@kbn/std'; import { PROJECTS } from './projects'; -import { buildAllTsRefs } from './build_ts_refs'; +import { buildTsRefs } from './build_ts_refs'; import { updateRootRefsConfig } from './root_refs_config'; export async function runTypeCheckCli() { @@ -26,11 +26,6 @@ export async function runTypeCheckCli() { // a reference to every composite project in the repo await updateRootRefsConfig(log); - const { failed } = await buildAllTsRefs({ log, procRunner, verbose: !!flags.verbose }); - if (failed) { - throw createFailError('Unable to build TS project refs'); - } - const projectFilter = flags.project && typeof flags.project === 'string' ? Path.resolve(flags.project) @@ -40,6 +35,16 @@ export async function runTypeCheckCli() { return !p.disableTypeCheck && (!projectFilter || p.tsConfigPath === projectFilter); }); + const { failed } = await buildTsRefs({ + log, + procRunner, + verbose: !!flags.verbose, + project: projects.length === 1 ? projects[0] : undefined, + }); + if (failed) { + throw createFailError('Unable to build TS project refs'); + } + if (!projects.length) { if (projectFilter) { throw createFailError(`Unable to find project at ${flags.project}`); diff --git a/src/plugins/bfetch/common/util/index.ts b/src/plugins/bfetch/common/util/index.ts index 1651d24d96b14..f20d30eb3cdf0 100644 --- a/src/plugins/bfetch/common/util/index.ts +++ b/src/plugins/bfetch/common/util/index.ts @@ -8,3 +8,4 @@ export * from './normalize_error'; export * from './remove_leading_slash'; +export * from './query_params'; diff --git a/src/plugins/bfetch/common/util/query_params.ts b/src/plugins/bfetch/common/util/query_params.ts new file mode 100644 index 0000000000000..ed65699fbcafc --- /dev/null +++ b/src/plugins/bfetch/common/util/query_params.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 appendQueryParam = (url: string, key: string, value: string): string => { + const separator = url.includes('?') ? '&' : '?'; + return `${url}${separator}${key}=${value}`; +}; diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts index 0b6dbe49d0e81..32adc0d7df0cf 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts @@ -9,7 +9,7 @@ import { createStreamingBatchedFunction } from './create_streaming_batched_function'; import { fetchStreaming as fetchStreamingReal } from '../streaming/fetch_streaming'; import { AbortError, defer, of } from '../../../kibana_utils/public'; -import { Subject, of as rxof } from 'rxjs'; +import { Subject } from 'rxjs'; const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); @@ -61,7 +61,7 @@ describe('createStreamingBatchedFunction()', () => { const fn = createStreamingBatchedFunction({ url: '/test', fetchStreaming, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); expect(typeof fn).toBe('function'); }); @@ -71,7 +71,7 @@ describe('createStreamingBatchedFunction()', () => { const fn = createStreamingBatchedFunction({ url: '/test', fetchStreaming, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const res = fn({}); expect(typeof res.then).toBe('function'); @@ -85,7 +85,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); expect(fetchStreaming).toHaveBeenCalledTimes(0); @@ -105,7 +105,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); expect(fetchStreaming).toHaveBeenCalledTimes(0); @@ -120,7 +120,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); fn({ foo: 'bar' }); @@ -139,7 +139,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); fn({ foo: 'bar' }); @@ -161,7 +161,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); expect(fetchStreaming).toHaveBeenCalledTimes(0); @@ -180,7 +180,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const abortController = new AbortController(); @@ -203,7 +203,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); fn({ a: '1' }); @@ -227,7 +227,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); fn({ a: '1' }); @@ -248,7 +248,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const promise1 = fn({ a: '1' }); @@ -266,7 +266,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); await flushPromises(); @@ -310,7 +310,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const promise1 = fn({ a: '1' }); @@ -348,7 +348,7 @@ describe('createStreamingBatchedFunction()', () => { fn({ a: '1' }); - const dontCompress = await fetchStreaming.mock.calls[0][0].compressionDisabled$.toPromise(); + const dontCompress = await fetchStreaming.mock.calls[0][0].getIsCompressionDisabled(); expect(dontCompress).toBe(false); }); @@ -359,7 +359,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const promise1 = fn({ a: '1' }); @@ -401,7 +401,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const promise = fn({ a: '1' }); @@ -430,7 +430,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const promise1 = of(fn({ a: '1' })); @@ -483,7 +483,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const abortController = new AbortController(); @@ -514,7 +514,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const abortController = new AbortController(); @@ -554,7 +554,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const promise1 = of(fn({ a: '1' })); @@ -585,7 +585,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const promise1 = of(fn({ a: '1' })); @@ -623,7 +623,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const promise1 = of(fn({ a: '1' })); @@ -656,7 +656,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); const promise1 = of(fn({ a: '1' })); @@ -693,7 +693,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, - compressionDisabled$: rxof(true), + getIsCompressionDisabled: () => true, }); await flushPromises(); diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts index d5f955f517d13..3ff8da08cfce7 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { Observable, of } from 'rxjs'; import { AbortError, abortSignalToPromise, defer } from '../../../kibana_utils/public'; import { ItemBufferParams, @@ -51,7 +50,7 @@ export interface StreamingBatchedFunctionParams { /** * Disabled zlib compression of response chunks. */ - compressionDisabled$?: Observable; + getIsCompressionDisabled?: () => boolean; } /** @@ -69,7 +68,7 @@ export const createStreamingBatchedFunction = ( fetchStreaming: fetchStreamingInjected = fetchStreaming, flushOnMaxItems = 25, maxItemAge = 10, - compressionDisabled$ = of(false), + getIsCompressionDisabled = () => false, } = params; const [fn] = createBatchedFunction({ onCall: (payload: Payload, signal?: AbortSignal) => { @@ -125,7 +124,7 @@ export const createStreamingBatchedFunction = ( body: JSON.stringify({ batch }), method: 'POST', signal: abortController.signal, - compressionDisabled$, + getIsCompressionDisabled, }); const handleStreamError = (error: any) => { diff --git a/src/plugins/bfetch/public/plugin.ts b/src/plugins/bfetch/public/plugin.ts index 3ad451c7713ea..54bcb305d8675 100644 --- a/src/plugins/bfetch/public/plugin.ts +++ b/src/plugins/bfetch/public/plugin.ts @@ -6,13 +6,12 @@ * Side Public License, v 1. */ -import { CoreStart, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/public'; -import { from, Observable, of } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { fetchStreaming as fetchStreamingStatic, FetchStreamingParams } from './streaming'; import { DISABLE_BFETCH_COMPRESSION, removeLeadingSlash } from '../common'; import { createStreamingBatchedFunction, StreamingBatchedFunctionParams } from './batching'; import { BatchedFunc } from './batching/types'; +import { createStartServicesGetter } from '../../kibana_utils/public'; // eslint-disable-next-line export interface BfetchPublicSetupDependencies {} @@ -50,16 +49,12 @@ export class BfetchPublicPlugin const { version } = this.initializerContext.env.packageInfo; const basePath = core.http.basePath.get(); - const compressionDisabled$ = from(core.getStartServices()).pipe( - switchMap((deps) => { - return of(deps[0]); - }), - switchMap((coreStart) => { - return coreStart.uiSettings.get$(DISABLE_BFETCH_COMPRESSION); - }) - ); - const fetchStreaming = this.fetchStreaming(version, basePath, compressionDisabled$); - const batchedFunction = this.batchedFunction(fetchStreaming, compressionDisabled$); + const startServices = createStartServicesGetter(core.getStartServices); + const getIsCompressionDisabled = () => + startServices().core.uiSettings.get(DISABLE_BFETCH_COMPRESSION); + + const fetchStreaming = this.fetchStreaming(version, basePath, getIsCompressionDisabled); + const batchedFunction = this.batchedFunction(fetchStreaming, getIsCompressionDisabled); this.contract = { fetchStreaming, @@ -79,7 +74,7 @@ export class BfetchPublicPlugin ( version: string, basePath: string, - compressionDisabled$: Observable + getIsCompressionDisabled: () => boolean ): BfetchPublicSetup['fetchStreaming'] => (params) => fetchStreamingStatic({ @@ -90,18 +85,18 @@ export class BfetchPublicPlugin 'kbn-version': version, ...(params.headers || {}), }, - compressionDisabled$, + getIsCompressionDisabled, }); private batchedFunction = ( fetchStreaming: BfetchPublicContract['fetchStreaming'], - compressionDisabled$: Observable + getIsCompressionDisabled: () => boolean ): BfetchPublicContract['batchedFunction'] => (params) => createStreamingBatchedFunction({ ...params, - compressionDisabled$, + getIsCompressionDisabled, fetchStreaming: params.fetchStreaming || fetchStreaming, }); } diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts index a5d066f6d9a24..67ebf8d5a1c23 100644 --- a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts @@ -8,7 +8,6 @@ import { fetchStreaming } from './fetch_streaming'; import { mockXMLHttpRequest } from '../test_helpers/xhr'; -import { of } from 'rxjs'; import { promisify } from 'util'; import { deflate } from 'zlib'; const pDeflate = promisify(deflate); @@ -30,7 +29,7 @@ test('returns XHR request', () => { setup(); const { xhr } = fetchStreaming({ url: 'http://example.com', - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); expect(typeof xhr.readyState).toBe('number'); }); @@ -39,7 +38,7 @@ test('returns stream', () => { setup(); const { stream } = fetchStreaming({ url: 'http://example.com', - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); expect(typeof stream.subscribe).toBe('function'); }); @@ -48,7 +47,7 @@ test('promise resolves when request completes', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); let resolved = false; @@ -81,7 +80,7 @@ test('promise resolves when compressed request completes', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', - compressionDisabled$: of(false), + getIsCompressionDisabled: () => false, }); let resolved = false; @@ -116,7 +115,7 @@ test('promise resolves when compressed chunked request completes', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', - compressionDisabled$: of(false), + getIsCompressionDisabled: () => false, }); let resolved = false; @@ -160,7 +159,7 @@ test('streams incoming text as it comes through, according to separators', async const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); const spy = jest.fn(); @@ -201,7 +200,7 @@ test('completes stream observable when request finishes', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); const spy = jest.fn(); @@ -226,7 +225,7 @@ test('completes stream observable when aborted', async () => { const { stream } = fetchStreaming({ url: 'http://example.com', signal: abort.signal, - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); const spy = jest.fn(); @@ -252,7 +251,7 @@ test('promise throws when request errors', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); const spy = jest.fn(); @@ -279,7 +278,7 @@ test('stream observable errors when request errors', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); const spy = jest.fn(); @@ -312,7 +311,7 @@ test('sets custom headers', async () => { 'Content-Type': 'text/plain', Authorization: 'Bearer 123', }, - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); expect(env.xhr.setRequestHeader).toHaveBeenCalledWith('Content-Type', 'text/plain'); @@ -326,7 +325,7 @@ test('uses credentials', async () => { fetchStreaming({ url: 'http://example.com', - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); expect(env.xhr.withCredentials).toBe(true); @@ -342,7 +341,7 @@ test('opens XHR request and sends specified body', async () => { url: 'http://elastic.co', method: 'GET', body: 'foobar', - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); expect(env.xhr.open).toHaveBeenCalledTimes(1); @@ -355,7 +354,7 @@ test('uses POST request method by default', async () => { const env = setup(); fetchStreaming({ url: 'http://elastic.co', - compressionDisabled$: of(true), + getIsCompressionDisabled: () => true, }); expect(env.xhr.open).toHaveBeenCalledWith('POST', 'http://elastic.co'); }); diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.ts index 1af35ef68fb85..77e5acffc1af3 100644 --- a/src/plugins/bfetch/public/streaming/fetch_streaming.ts +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { Observable, of } from 'rxjs'; -import { map, share, switchMap } from 'rxjs/operators'; +import { map, share } from 'rxjs/operators'; import { inflateResponse } from '.'; import { fromStreamingXhr } from './from_streaming_xhr'; import { split } from './split'; +import { appendQueryParam } from '../../common'; export interface FetchStreamingParams { url: string; @@ -18,7 +18,7 @@ export interface FetchStreamingParams { method?: 'GET' | 'POST'; body?: string; signal?: AbortSignal; - compressionDisabled$?: Observable; + getIsCompressionDisabled?: () => boolean; } /** @@ -31,49 +31,38 @@ export function fetchStreaming({ method = 'POST', body = '', signal, - compressionDisabled$ = of(false), + getIsCompressionDisabled = () => false, }: FetchStreamingParams) { const xhr = new window.XMLHttpRequest(); - const msgStream = compressionDisabled$.pipe( - switchMap((compressionDisabled) => { - // Begin the request - xhr.open(method, url); - xhr.withCredentials = true; + const isCompressionDisabled = getIsCompressionDisabled(); + if (!isCompressionDisabled) { + url = appendQueryParam(url, 'compress', 'true'); + } - if (!compressionDisabled) { - headers['X-Chunk-Encoding'] = 'deflate'; - } + // Begin the request + xhr.open(method, url); + xhr.withCredentials = true; - // Set the HTTP headers - Object.entries(headers).forEach(([k, v]) => xhr.setRequestHeader(k, v)); + // Set the HTTP headers + Object.entries(headers).forEach(([k, v]) => xhr.setRequestHeader(k, v)); - const stream = fromStreamingXhr(xhr, signal); + const stream = fromStreamingXhr(xhr, signal); - // Send the payload to the server - xhr.send(body); + // Send the payload to the server + xhr.send(body); - // Return a stream of chunked decompressed messages - return stream.pipe( - split('\n'), - map((msg) => { - return compressionDisabled ? msg : inflateResponse(msg); - }) - ); + // Return a stream of chunked decompressed messages + const stream$ = stream.pipe( + split('\n'), + map((msg) => { + return isCompressionDisabled ? msg : inflateResponse(msg); }), share() ); - // start execution - const msgStreamSub = msgStream.subscribe({ - error: (e) => {}, - complete: () => { - msgStreamSub.unsubscribe(); - }, - }); - return { xhr, - stream: msgStream, + stream: stream$, }; } diff --git a/src/plugins/bfetch/server/index.ts b/src/plugins/bfetch/server/index.ts index c533b2ad7a3df..f4c41d10e42cb 100644 --- a/src/plugins/bfetch/server/index.ts +++ b/src/plugins/bfetch/server/index.ts @@ -10,7 +10,6 @@ import { PluginInitializerContext } from '../../../core/server'; import { BfetchServerPlugin } from './plugin'; export { BfetchServerSetup, BfetchServerStart, BatchProcessingRouteParams } from './plugin'; -export { StreamingRequestHandler } from './types'; export function plugin(initializerContext: PluginInitializerContext) { return new BfetchServerPlugin(initializerContext); diff --git a/src/plugins/bfetch/server/mocks.ts b/src/plugins/bfetch/server/mocks.ts index bbb596bf8d5ff..dfa365d9e70b2 100644 --- a/src/plugins/bfetch/server/mocks.ts +++ b/src/plugins/bfetch/server/mocks.ts @@ -17,7 +17,6 @@ const createSetupContract = (): Setup => { const setupContract: Setup = { addBatchProcessingRoute: jest.fn(), addStreamingResponseRoute: jest.fn(), - createStreamingRequestHandler: jest.fn(), }; return setupContract; }; diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts index 7b60be9a8fc75..f7127445f96c5 100644 --- a/src/plugins/bfetch/server/plugin.ts +++ b/src/plugins/bfetch/server/plugin.ts @@ -13,9 +13,6 @@ import type { Plugin, Logger, KibanaRequest, - RouteMethod, - RequestHandler, - RequestHandlerContext, StartServicesAccessor, } from 'src/core/server'; import { schema } from '@kbn/config-schema'; @@ -28,7 +25,6 @@ import { removeLeadingSlash, normalizeError, } from '../common'; -import { StreamingRequestHandler } from './types'; import { createStream } from './streaming'; import { getUiSettings } from './ui_settings'; @@ -52,44 +48,6 @@ export interface BfetchServerSetup { path: string, params: (request: KibanaRequest) => StreamingResponseHandler ) => void; - /** - * Create a streaming request handler to be able to use an Observable to return chunked content to the client. - * This is meant to be used with the `fetchStreaming` API of the `bfetch` client-side plugin. - * - * @example - * ```ts - * setup({ http }: CoreStart, { bfetch }: SetupDeps) { - * const router = http.createRouter(); - * router.post( - * { - * path: '/api/my-plugin/stream-endpoint, - * validate: { - * body: schema.object({ - * term: schema.string(), - * }), - * } - * }, - * bfetch.createStreamingResponseHandler(async (ctx, req) => { - * const { term } = req.body; - * const results$ = await myApi.getResults$(term); - * return results$; - * }) - * )} - * - * ``` - * - * @param streamHandler - */ - createStreamingRequestHandler: < - Response, - P, - Q, - B, - Context extends RequestHandlerContext = RequestHandlerContext, - Method extends RouteMethod = any - >( - streamHandler: StreamingRequestHandler - ) => RequestHandler; } // eslint-disable-next-line @@ -124,15 +82,10 @@ export class BfetchServerPlugin logger, }); const addBatchProcessingRoute = this.addBatchProcessingRoute(addStreamingResponseRoute); - const createStreamingRequestHandler = this.createStreamingRequestHandler({ - getStartServices: core.getStartServices, - logger, - }); return { addBatchProcessingRoute, addStreamingResponseRoute, - createStreamingRequestHandler, }; } @@ -142,10 +95,6 @@ export class BfetchServerPlugin public stop() {} - private getCompressionDisabled(request: KibanaRequest) { - return request.headers['x-chunk-encoding'] !== 'deflate'; - } - private addStreamingResponseRoute = ({ getStartServices, @@ -162,42 +111,21 @@ export class BfetchServerPlugin path: `/${removeLeadingSlash(path)}`, validate: { body: schema.any(), + query: schema.object({ compress: schema.boolean({ defaultValue: false }) }), }, }, async (context, request, response) => { const handlerInstance = handler(request); const data = request.body; - const compressionDisabled = this.getCompressionDisabled(request); + const compress = request.query.compress; return response.ok({ headers: streamingHeaders, - body: createStream( - handlerInstance.getResponseStream(data), - logger, - compressionDisabled - ), + body: createStream(handlerInstance.getResponseStream(data), logger, compress), }); } ); }; - private createStreamingRequestHandler = - ({ - logger, - getStartServices, - }: { - logger: Logger; - getStartServices: StartServicesAccessor; - }): BfetchServerSetup['createStreamingRequestHandler'] => - (streamHandler) => - async (context, request, response) => { - const response$ = await streamHandler(context, request); - const compressionDisabled = this.getCompressionDisabled(request); - return response.ok({ - headers: streamingHeaders, - body: createStream(response$, logger, compressionDisabled), - }); - }; - private addBatchProcessingRoute = ( addStreamingResponseRoute: BfetchServerSetup['addStreamingResponseRoute'] diff --git a/src/plugins/bfetch/server/streaming/create_stream.ts b/src/plugins/bfetch/server/streaming/create_stream.ts index 7d6981294341b..756a806a60229 100644 --- a/src/plugins/bfetch/server/streaming/create_stream.ts +++ b/src/plugins/bfetch/server/streaming/create_stream.ts @@ -15,9 +15,9 @@ import { createNDJSONStream } from './create_ndjson_stream'; export function createStream( response$: Observable, logger: Logger, - compressionDisabled: boolean + compress: boolean ): Stream { - return compressionDisabled - ? createNDJSONStream(response$, logger) - : createCompressedStream(response$, logger); + return compress + ? createCompressedStream(response$, logger) + : createNDJSONStream(response$, logger); } diff --git a/src/plugins/bfetch/server/types.ts b/src/plugins/bfetch/server/types.ts deleted file mode 100644 index 4e54744f4c374..0000000000000 --- a/src/plugins/bfetch/server/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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 { Observable } from 'rxjs'; -import { KibanaRequest, RequestHandlerContext, RouteMethod } from 'kibana/server'; - -/** - * Request handler modified to allow to return an observable. - * - * See {@link BfetchServerSetup.createStreamingRequestHandler} for usage example. - * @public - */ -export type StreamingRequestHandler< - Response = unknown, - P = unknown, - Q = unknown, - B = unknown, - Method extends RouteMethod = any -> = ( - context: RequestHandlerContext, - request: KibanaRequest -) => Observable | Promise>; diff --git a/src/plugins/chart_expressions/expression_metric/.i18nrc.json b/src/plugins/chart_expressions/expression_metric/.i18nrc.json new file mode 100755 index 0000000000000..a107bad97f278 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/.i18nrc.json @@ -0,0 +1,6 @@ +{ + "prefix": "expressionMetricVis", + "paths": { + "expressionMetricVis": "." + } +} diff --git a/src/plugins/chart_expressions/expression_metric/.storybook/main.js b/src/plugins/chart_expressions/expression_metric/.storybook/main.js new file mode 100644 index 0000000000000..cb483d5394285 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/.storybook/main.js @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { defaultConfig } from '@kbn/storybook'; +import webpackMerge from 'webpack-merge'; +import { resolve } from 'path'; + +const mockConfig = { + resolve: { + alias: { + '../format_service': resolve(__dirname, '../public/__mocks__/format_service.ts'), + }, + }, +}; + +module.exports = { + ...defaultConfig, + webpackFinal: (config) => webpackMerge(config, mockConfig), +}; diff --git a/src/plugins/chart_expressions/expression_metric/README.md b/src/plugins/chart_expressions/expression_metric/README.md new file mode 100755 index 0000000000000..ec2541ebf74d8 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/README.md @@ -0,0 +1,9 @@ +# expressionMetricVis + +Expression MetricVis plugin adds a `metric` renderer and function to the expression plugin. The renderer will display the `metric` chart. + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/chart_expressions/expression_metric/common/constants.ts b/src/plugins/chart_expressions/expression_metric/common/constants.ts new file mode 100644 index 0000000000000..b39902f61ac45 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/common/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 EXPRESSION_METRIC_NAME = 'metricVis'; diff --git a/src/plugins/vis_types/metric/public/__snapshots__/metric_vis_fn.test.ts.snap b/src/plugins/chart_expressions/expression_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap similarity index 98% rename from src/plugins/vis_types/metric/public/__snapshots__/metric_vis_fn.test.ts.snap rename to src/plugins/chart_expressions/expression_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap index a3be4d10d9bfd..03055764cc4a4 100644 --- a/src/plugins/vis_types/metric/public/__snapshots__/metric_vis_fn.test.ts.snap +++ b/src/plugins/chart_expressions/expression_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap @@ -22,7 +22,7 @@ Object { exports[`interpreter/functions#metric returns an object with the correct structure 1`] = ` Object { - "as": "metric_vis", + "as": "metricVis", "type": "render", "value": Object { "visConfig": Object { diff --git a/src/plugins/chart_expressions/expression_metric/common/expression_functions/index.ts b/src/plugins/chart_expressions/expression_metric/common/expression_functions/index.ts new file mode 100644 index 0000000000000..5eccaa62fe464 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/common/expression_functions/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 { metricVisFunction } from './metric_vis_function'; diff --git a/src/plugins/vis_types/metric/public/metric_vis_fn.test.ts b/src/plugins/chart_expressions/expression_metric/common/expression_functions/metric_vis_function.test.ts similarity index 81% rename from src/plugins/vis_types/metric/public/metric_vis_fn.test.ts rename to src/plugins/chart_expressions/expression_metric/common/expression_functions/metric_vis_function.test.ts index 28124a653629c..1f90322e703b8 100644 --- a/src/plugins/vis_types/metric/public/metric_vis_fn.test.ts +++ b/src/plugins/chart_expressions/expression_metric/common/expression_functions/metric_vis_function.test.ts @@ -6,14 +6,13 @@ * Side Public License, v 1. */ -import { createMetricVisFn } from './metric_vis_fn'; -import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils'; -import { Datatable } from '../../../expressions/common/expression_types/specs'; - -type Arguments = Parameters['fn']>[1]; +import { metricVisFunction } from './metric_vis_function'; +import type { MetricArguments } from '../../common'; +import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils'; +import { Datatable } from '../../../../expressions/common/expression_types/specs'; describe('interpreter/functions#metric', () => { - const fn = functionWrapper(createMetricVisFn()); + const fn = functionWrapper(metricVisFunction()); const context = { type: 'datatable', rows: [{ 'col-0-1': 0 }], @@ -52,7 +51,7 @@ describe('interpreter/functions#metric', () => { aggType: 'count', }, ], - } as unknown as Arguments; + } as unknown as MetricArguments; it('returns an object with the correct structure', () => { const actual = fn(context, args, undefined); diff --git a/src/plugins/vis_types/metric/public/metric_vis_fn.ts b/src/plugins/chart_expressions/expression_metric/common/expression_functions/metric_vis_function.ts similarity index 66% rename from src/plugins/vis_types/metric/public/metric_vis_fn.ts rename to src/plugins/chart_expressions/expression_metric/common/expression_functions/metric_vis_function.ts index 210552732bc0a..31f5b8421b3a6 100644 --- a/src/plugins/vis_types/metric/public/metric_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_metric/common/expression_functions/metric_vis_function.ts @@ -8,60 +8,24 @@ import { i18n } from '@kbn/i18n'; -import { - ExpressionFunctionDefinition, - Datatable, - Range, - Render, - Style, -} from '../../../expressions/public'; -import { visType, VisParams } from './types'; -import { prepareLogTable, Dimension } from '../../../visualizations/public'; -import { ColorSchemas, vislibColorMaps, ColorMode } from '../../../charts/public'; -import { ExpressionValueVisDimension } from '../../../visualizations/public'; +import { visType } from '../types'; +import { prepareLogTable, Dimension } from '../../../../visualizations/common/prepare_log_table'; +import { vislibColorMaps, ColorMode } from '../../../../charts/common'; +import { MetricVisExpressionFunctionDefinition } from '../types'; +import { EXPRESSION_METRIC_NAME } from '../constants'; -export type Input = Datatable; - -interface Arguments { - percentageMode: boolean; - colorSchema: ColorSchemas; - colorMode: ColorMode; - useRanges: boolean; - invertColors: boolean; - showLabels: boolean; - bgFill: string; - subText: string; - colorRange: Range[]; - font: Style; - metric: ExpressionValueVisDimension[]; - bucket: ExpressionValueVisDimension; -} - -export interface MetricVisRenderValue { - visType: typeof visType; - visData: Input; - visConfig: Pick; -} - -export type MetricVisExpressionFunctionDefinition = ExpressionFunctionDefinition< - 'metricVis', - Input, - Arguments, - Render ->; - -export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ - name: 'metricVis', +export const metricVisFunction = (): MetricVisExpressionFunctionDefinition => ({ + name: EXPRESSION_METRIC_NAME, type: 'render', inputTypes: ['datatable'], - help: i18n.translate('visTypeMetric.function.help', { + help: i18n.translate('expressionMetricVis.function.help', { defaultMessage: 'Metric visualization', }), args: { percentageMode: { types: ['boolean'], default: false, - help: i18n.translate('visTypeMetric.function.percentageMode.help', { + help: i18n.translate('expressionMetricVis.function.percentageMode.help', { defaultMessage: 'Shows metric in percentage mode. Requires colorRange to be set.', }), }, @@ -69,7 +33,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ types: ['string'], default: '"Green to Red"', options: Object.values(vislibColorMaps).map((value: any) => value.id), - help: i18n.translate('visTypeMetric.function.colorSchema.help', { + help: i18n.translate('expressionMetricVis.function.colorSchema.help', { defaultMessage: 'Color schema to use', }), }, @@ -77,7 +41,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ types: ['string'], default: '"None"', options: [ColorMode.None, ColorMode.Labels, ColorMode.Background], - help: i18n.translate('visTypeMetric.function.colorMode.help', { + help: i18n.translate('expressionMetricVis.function.colorMode.help', { defaultMessage: 'Which part of metric to color', }), }, @@ -85,7 +49,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ types: ['range'], multi: true, default: '{range from=0 to=10000}', - help: i18n.translate('visTypeMetric.function.colorRange.help', { + help: i18n.translate('expressionMetricVis.function.colorRange.help', { defaultMessage: 'A range object specifying groups of values to which different colors should be applied.', }), @@ -93,21 +57,21 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ useRanges: { types: ['boolean'], default: false, - help: i18n.translate('visTypeMetric.function.useRanges.help', { + help: i18n.translate('expressionMetricVis.function.useRanges.help', { defaultMessage: 'Enabled color ranges.', }), }, invertColors: { types: ['boolean'], default: false, - help: i18n.translate('visTypeMetric.function.invertColors.help', { + help: i18n.translate('expressionMetricVis.function.invertColors.help', { defaultMessage: 'Inverts the color ranges', }), }, showLabels: { types: ['boolean'], default: true, - help: i18n.translate('visTypeMetric.function.showLabels.help', { + help: i18n.translate('expressionMetricVis.function.showLabels.help', { defaultMessage: 'Shows labels under the metric values.', }), }, @@ -115,14 +79,14 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ types: ['string'], default: '"#000"', aliases: ['backgroundFill', 'bgColor', 'backgroundColor'], - help: i18n.translate('visTypeMetric.function.bgFill.help', { + help: i18n.translate('expressionMetricVis.function.bgFill.help', { defaultMessage: 'Color as html hex code (#123456), html color (red, blue) or rgba value (rgba(255,255,255,1)).', }), }, font: { types: ['style'], - help: i18n.translate('visTypeMetric.function.font.help', { + help: i18n.translate('expressionMetricVis.function.font.help', { defaultMessage: 'Font settings.', }), default: '{font size=60}', @@ -131,13 +95,13 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ types: ['string'], aliases: ['label', 'text', 'description'], default: '""', - help: i18n.translate('visTypeMetric.function.subText.help', { + help: i18n.translate('expressionMetricVis.function.subText.help', { defaultMessage: 'Custom text to show under the metric', }), }, metric: { types: ['vis_dimension'], - help: i18n.translate('visTypeMetric.function.metric.help', { + help: i18n.translate('expressionMetricVis.function.metric.help', { defaultMessage: 'metric dimension configuration', }), required: true, @@ -145,7 +109,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ }, bucket: { types: ['vis_dimension'], - help: i18n.translate('visTypeMetric.function.bucket.help', { + help: i18n.translate('expressionMetricVis.function.bucket.help', { defaultMessage: 'bucket dimension configuration', }), }, @@ -161,7 +125,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ const argsTable: Dimension[] = [ [ args.metric, - i18n.translate('visTypeMetric.function.dimension.metric', { + i18n.translate('expressionMetricVis.function.dimension.metric', { defaultMessage: 'Metric', }), ], @@ -169,7 +133,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ if (args.bucket) { argsTable.push([ [args.bucket], - i18n.translate('visTypeMetric.function.adimension.splitGroup', { + i18n.translate('expressionMetricVis.function.dimension.splitGroup', { defaultMessage: 'Split group', }), ]); @@ -180,7 +144,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ return { type: 'render', - as: 'metric_vis', + as: EXPRESSION_METRIC_NAME, value: { visData: input, visType, diff --git a/src/plugins/chart_expressions/expression_metric/common/index.ts b/src/plugins/chart_expressions/expression_metric/common/index.ts new file mode 100755 index 0000000000000..ee023dca2f4ff --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/common/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const PLUGIN_ID = 'expressionMetricVis'; +export const PLUGIN_NAME = 'expressionMetricVis'; + +export type { + MetricArguments, + MetricInput, + MetricVisRenderConfig, + MetricVisExpressionFunctionDefinition, + DimensionsVisParam, + MetricVisParam, + VisParams, + MetricOptions, +} from './types'; + +export { metricVisFunction } from './expression_functions'; + +export { EXPRESSION_METRIC_NAME } from './constants'; diff --git a/src/plugins/chart_expressions/expression_metric/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_metric/common/types/expression_functions.ts new file mode 100644 index 0000000000000..5e8b01ec93005 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/common/types/expression_functions.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { + Datatable, + ExpressionFunctionDefinition, + Range, + ExpressionValueRender, + Style, +} from '../../../../expressions'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; +import { ColorSchemas, ColorMode } from '../../../../charts/common'; +import { VisParams, visType } from './expression_renderers'; +import { EXPRESSION_METRIC_NAME } from '../constants'; + +export interface MetricArguments { + percentageMode: boolean; + colorSchema: ColorSchemas; + colorMode: ColorMode; + useRanges: boolean; + invertColors: boolean; + showLabels: boolean; + bgFill: string; + subText: string; + colorRange: Range[]; + font: Style; + metric: ExpressionValueVisDimension[]; + bucket: ExpressionValueVisDimension; +} + +export type MetricInput = Datatable; + +export interface MetricVisRenderConfig { + visType: typeof visType; + visData: MetricInput; + visConfig: Pick; +} + +export type MetricVisExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof EXPRESSION_METRIC_NAME, + MetricInput, + MetricArguments, + ExpressionValueRender +>; diff --git a/src/plugins/chart_expressions/expression_metric/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_metric/common/types/expression_renderers.ts new file mode 100644 index 0000000000000..2cc7ce853f8bf --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/common/types/expression_renderers.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { Range } from '../../../../expressions/common'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; +import { ColorMode, Labels, Style, ColorSchemas } from '../../../../charts/common'; + +export const visType = 'metric'; + +export interface DimensionsVisParam { + metrics: ExpressionValueVisDimension[]; + bucket?: ExpressionValueVisDimension; +} + +export interface MetricVisParam { + percentageMode: boolean; + percentageFormatPattern?: string; + useRanges: boolean; + colorSchema: ColorSchemas; + metricColorMode: ColorMode; + colorsRange: Range[]; + labels: Labels; + invertColors: boolean; + style: Style; +} + +export interface VisParams { + addTooltip: boolean; + addLegend: boolean; + dimensions: DimensionsVisParam; + metric: MetricVisParam; + type: typeof visType; +} + +export interface MetricOptions { + value: string; + label: string; + color?: string; + bgColor?: string; + lightText: boolean; + rowIndex: number; +} diff --git a/src/plugins/chart_expressions/expression_metric/common/types/index.ts b/src/plugins/chart_expressions/expression_metric/common/types/index.ts new file mode 100644 index 0000000000000..9c50bfab1305d --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/common/types/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './expression_functions'; +export * from './expression_renderers'; diff --git a/src/plugins/chart_expressions/expression_metric/jest.config.js b/src/plugins/chart_expressions/expression_metric/jest.config.js new file mode 100644 index 0000000000000..d737b45f34e69 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/jest.config.js @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../', + roots: ['/src/plugins/chart_expressions/expression_metric'], + coverageDirectory: + '/target/kibana-coverage/jest/src/plugins/chart_expressions/expression_metric', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/src/plugins/chart_expressions/expression_metric/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/src/plugins/chart_expressions/expression_metric/kibana.json b/src/plugins/chart_expressions/expression_metric/kibana.json new file mode 100755 index 0000000000000..c662dc1310323 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/kibana.json @@ -0,0 +1,15 @@ +{ + "id": "expressionMetricVis", + "version": "1.0.0", + "kibanaVersion": "kibana", + "owner": { + "name": "Vis Editors", + "githubTeam": "kibana-vis-editors" + }, + "description": "Expression MetricVis plugin adds a `metric` renderer and function to the expression plugin. The renderer will display the `metric` chart.", + "server": true, + "ui": true, + "requiredPlugins": ["expressions", "fieldFormats", "charts", "visualizations", "presentationUtil"], + "requiredBundles": ["kibanaUtils"], + "optionalPlugins": [] +} diff --git a/src/plugins/chart_expressions/expression_metric/public/__mocks__/format_service.ts b/src/plugins/chart_expressions/expression_metric/public/__mocks__/format_service.ts new file mode 100644 index 0000000000000..77f6d8eb0bf37 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/public/__mocks__/format_service.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 getFormatService = () => ({ + deserialize: (target: any) => ({ + convert: (text: string, format: string) => text, + }), +}); diff --git a/src/plugins/chart_expressions/expression_metric/public/__stories__/metric_renderer.stories.tsx b/src/plugins/chart_expressions/expression_metric/public/__stories__/metric_renderer.stories.tsx new file mode 100644 index 0000000000000..b22616af01c91 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/public/__stories__/metric_renderer.stories.tsx @@ -0,0 +1,250 @@ +/* + * Copyright 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 { storiesOf } from '@storybook/react'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; +import { DatatableColumn, Range } from '../../../../expressions'; +import { Render } from '../../../../presentation_util/public/__stories__'; +import { ColorMode, ColorSchemas } from '../../../../charts/common'; +import { metricVisRenderer } from '../expression_renderers'; +import { MetricVisRenderConfig, visType } from '../../common/types'; + +const config: MetricVisRenderConfig = { + visType, + visData: { + type: 'datatable', + rows: [{ 'col-0-1': 85, 'col-0-2': 30 }], + columns: [ + { + id: 'col-0-1', + name: 'Max products count', + meta: { type: 'number', params: {} }, + }, + { + id: 'col-0-2', + name: 'Median products count', + meta: { type: 'number', params: {} }, + }, + ], + }, + visConfig: { + metric: { + percentageMode: false, + useRanges: false, + colorSchema: ColorSchemas.GreenToRed, + metricColorMode: ColorMode.None, + colorsRange: [], + labels: { show: true }, + invertColors: false, + style: { + bgColor: false, + bgFill: '#000', + fontSize: 60, + labelColor: false, + subText: '', + }, + }, + dimensions: { + metrics: [ + { + accessor: 0, + format: { + id: 'number', + params: {}, + }, + type: 'vis_dimension', + }, + { + accessor: { + id: 'col-0-2', + name: 'Median products count', + meta: { type: 'number' }, + }, + format: { + id: 'number', + params: {}, + }, + type: 'vis_dimension', + }, + ], + }, + }, +}; + +const dayColumn: DatatableColumn = { + id: 'col-0-3', + name: 'Day of the week', + meta: { type: 'string', params: {} }, +}; + +const dayAccessor: ExpressionValueVisDimension = { + accessor: { + id: 'col-0-3', + name: 'Day of the week', + meta: { type: 'string' }, + }, + format: { + id: 'string', + params: {}, + }, + type: 'vis_dimension', +}; + +const dataWithBuckets = [ + { 'col-0-1': 85, 'col-0-2': 30, 'col-0-3': 'Monday' }, + { 'col-0-1': 55, 'col-0-2': 32, 'col-0-3': 'Tuesday' }, + { 'col-0-1': 56, 'col-0-2': 52, 'col-0-3': 'Wednesday' }, +]; + +const colorsRange: Range[] = [ + { type: 'range', from: 0, to: 50 }, + { type: 'range', from: 51, to: 150 }, +]; + +const containerSize = { + width: '700px', + height: '700px', +}; + +storiesOf('renderers/visMetric', module) + .add('Default', () => { + return ; + }) + .add('Without labels', () => { + return ( + + ); + }) + .add('With custom font size', () => { + return ( + + ); + }) + .add('With color ranges, background color mode', () => { + return ( + + ); + }) + .add('With color ranges, labels color mode', () => { + return ( + + ); + }) + .add('With color ranges, labels color mode, reverse mode', () => { + return ( + + ); + }) + .add('With bucket', () => { + return ( + + ); + }) + .add('With empty results', () => { + return ( + + ); + }); diff --git a/src/plugins/vis_types/metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap b/src/plugins/chart_expressions/expression_metric/public/components/__snapshots__/metric_component.test.tsx.snap similarity index 100% rename from src/plugins/vis_types/metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap rename to src/plugins/chart_expressions/expression_metric/public/components/__snapshots__/metric_component.test.tsx.snap diff --git a/src/plugins/vis_types/metric/public/components/metric_vis.scss b/src/plugins/chart_expressions/expression_metric/public/components/metric.scss similarity index 100% rename from src/plugins/vis_types/metric/public/components/metric_vis.scss rename to src/plugins/chart_expressions/expression_metric/public/components/metric.scss diff --git a/src/plugins/vis_types/metric/public/components/metric_vis_component.test.tsx b/src/plugins/chart_expressions/expression_metric/public/components/metric_component.test.tsx similarity index 97% rename from src/plugins/vis_types/metric/public/components/metric_vis_component.test.tsx rename to src/plugins/chart_expressions/expression_metric/public/components/metric_component.test.tsx index c66704c44ea0d..ec3b9aee8583c 100644 --- a/src/plugins/vis_types/metric/public/components/metric_vis_component.test.tsx +++ b/src/plugins/chart_expressions/expression_metric/public/components/metric_component.test.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { shallow } from 'enzyme'; -import MetricVisComponent, { MetricVisComponentProps } from './metric_vis_component'; +import MetricVisComponent, { MetricVisComponentProps } from './metric_component'; -jest.mock('../services', () => ({ +jest.mock('../format_service', () => ({ getFormatService: () => ({ deserialize: () => { return { diff --git a/src/plugins/vis_types/metric/public/components/metric_vis_component.tsx b/src/plugins/chart_expressions/expression_metric/public/components/metric_component.tsx similarity index 81% rename from src/plugins/vis_types/metric/public/components/metric_vis_component.tsx rename to src/plugins/chart_expressions/expression_metric/public/components/metric_component.tsx index 837ec5ff60dc5..4efdefc7d28ee 100644 --- a/src/plugins/vis_types/metric/public/components/metric_vis_component.tsx +++ b/src/plugins/chart_expressions/expression_metric/public/components/metric_component.tsx @@ -9,21 +9,19 @@ import { last, findIndex, isNaN } from 'lodash'; import React, { Component } from 'react'; import { isColorDark } from '@elastic/eui'; -import { MetricVisValue } from './metric_vis_value'; -import { Input } from '../metric_vis_fn'; +import { MetricVisValue } from './metric_value'; +import { MetricInput, VisParams, MetricOptions } from '../../common/types'; import type { FieldFormatsContentType, IFieldFormat } from '../../../../field_formats/common'; import { Datatable } from '../../../../expressions/public'; import { getHeatmapColors } from '../../../../charts/public'; -import { VisParams, MetricVisMetric } from '../types'; -import { getFormatService } from '../services'; +import { getFormatService } from '../format_service'; import { ExpressionValueVisDimension } from '../../../../visualizations/public'; -import { Range } from '../../../../expressions/public'; -import './metric_vis.scss'; +import './metric.scss'; export interface MetricVisComponentProps { visParams: Pick; - visData: Input; + visData: MetricInput; fireEvent: (event: any) => void; renderComplete: () => void; } @@ -33,7 +31,7 @@ class MetricVisComponent extends Component { const config = this.props.visParams.metric; const isPercentageMode = config.percentageMode; const colorsRange = config.colorsRange; - const max = (last(colorsRange) as Range).to; + const max = last(colorsRange)?.to ?? 1; const labels: string[] = []; colorsRange.forEach((range: any) => { @@ -66,7 +64,7 @@ class MetricVisComponent extends Component { }); if (bucket === -1) { - if (val < config.colorsRange[0].from) bucket = 0; + if (config.colorsRange?.[0] && val < config.colorsRange?.[0].from) bucket = 0; else bucket = config.colorsRange.length - 1; } @@ -109,14 +107,13 @@ class MetricVisComponent extends Component { } private processTableGroups(table: Datatable) { - const config = this.props.visParams.metric; - const dimensions = this.props.visParams.dimensions; - const isPercentageMode = config.percentageMode; - const min = config.colorsRange[0].from; - const max = (last(config.colorsRange) as Range).to; + const { metric: metricConfig, dimensions } = this.props.visParams; + const { percentageMode: isPercentageMode, colorsRange, style } = metricConfig; + const min = colorsRange?.[0]?.from; + const max = last(colorsRange)?.to; const colors = this.getColors(); const labels = this.getLabels(); - const metrics: MetricVisMetric[] = []; + const metrics: MetricOptions[] = []; let bucketColumnId: string; let bucketFormatter: IFieldFormat; @@ -131,27 +128,26 @@ class MetricVisComponent extends Component { const formatter = getFormatService().deserialize(metric.format); table.rows.forEach((row, rowIndex) => { let title = column.name; - let value: any = row[column.id]; + let value: number = row[column.id]; const color = this.getColor(value, labels, colors); - if (isPercentageMode) { + if (isPercentageMode && colorsRange?.length && max !== undefined && min !== undefined) { value = (value - min) / (max - min); } - value = this.getFormattedValue(formatter, value, 'html'); - + const formattedValue = this.getFormattedValue(formatter, value, 'html'); if (bucketColumnId) { const bucketValue = this.getFormattedValue(bucketFormatter, row[bucketColumnId]); title = `${bucketValue} - ${title}`; } - const shouldColor = config.colorsRange.length > 1; + const shouldColor = colorsRange && colorsRange.length > 1; metrics.push({ label: title, - value, - color: shouldColor && config.style.labelColor ? color : undefined, - bgColor: shouldColor && config.style.bgColor ? color : undefined, - lightText: shouldColor && config.style.bgColor && this.needsLightText(color), + value: formattedValue, + color: shouldColor && style.labelColor ? color : undefined, + bgColor: shouldColor && style.bgColor ? color : undefined, + lightText: shouldColor && style.bgColor && this.needsLightText(color), rowIndex, }); }); @@ -160,7 +156,7 @@ class MetricVisComponent extends Component { return metrics; } - private filterBucket = (metric: MetricVisMetric) => { + private filterBucket = (metric: MetricOptions) => { const dimensions = this.props.visParams.dimensions; if (!dimensions.bucket) { return; @@ -180,7 +176,7 @@ class MetricVisComponent extends Component { }); }; - private renderMetric = (metric: MetricVisMetric, index: number) => { + private renderMetric = (metric: MetricOptions, index: number) => { return ( void; + onFilter?: (metric: MetricOptions) => void; showLabel?: boolean; } diff --git a/src/plugins/chart_expressions/expression_metric/public/expression_renderers/index.ts b/src/plugins/chart_expressions/expression_metric/public/expression_renderers/index.ts new file mode 100644 index 0000000000000..b4fb6cea84aa3 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/public/expression_renderers/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 { metricVisRenderer } from './metric_vis_renderer'; diff --git a/src/plugins/vis_types/metric/public/metric_vis_renderer.tsx b/src/plugins/chart_expressions/expression_metric/public/expression_renderers/metric_vis_renderer.tsx similarity index 73% rename from src/plugins/vis_types/metric/public/metric_vis_renderer.tsx rename to src/plugins/chart_expressions/expression_metric/public/expression_renderers/metric_vis_renderer.tsx index 0bd2efbfe2efb..6c3c7696fca40 100644 --- a/src/plugins/vis_types/metric/public/metric_vis_renderer.tsx +++ b/src/plugins/chart_expressions/expression_metric/public/expression_renderers/metric_vis_renderer.tsx @@ -9,14 +9,15 @@ import React, { lazy } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { VisualizationContainer } from '../../../visualizations/public'; -import { ExpressionRenderDefinition } from '../../../expressions/common/expression_renderers'; -import { MetricVisRenderValue } from './metric_vis_fn'; +import { VisualizationContainer } from '../../../../visualizations/public'; +import { ExpressionRenderDefinition } from '../../../../expressions/common/expression_renderers'; +import { EXPRESSION_METRIC_NAME, MetricVisRenderConfig } from '../../common'; + // @ts-ignore -const MetricVisComponent = lazy(() => import('./components/metric_vis_component')); +const MetricVisComponent = lazy(() => import('../components/metric_component')); -export const metricVisRenderer: () => ExpressionRenderDefinition = () => ({ - name: 'metric_vis', +export const metricVisRenderer: () => ExpressionRenderDefinition = () => ({ + name: EXPRESSION_METRIC_NAME, displayName: 'metric visualization', reuseDomNode: true, render: async (domNode, { visData, visConfig }, handlers) => { diff --git a/src/plugins/vis_types/metric/public/services.ts b/src/plugins/chart_expressions/expression_metric/public/format_service.ts similarity index 57% rename from src/plugins/vis_types/metric/public/services.ts rename to src/plugins/chart_expressions/expression_metric/public/format_service.ts index e705513675e71..19d2f9a5568ce 100644 --- a/src/plugins/vis_types/metric/public/services.ts +++ b/src/plugins/chart_expressions/expression_metric/public/format_service.ts @@ -6,9 +6,8 @@ * Side Public License, v 1. */ -import { createGetterSetter } from '../../../kibana_utils/common'; -import { DataPublicPluginStart } from '../../../data/public'; +import { createGetterSetter } from '../../../kibana_utils/public'; +import { FieldFormatsStart } from '../../../field_formats/public'; -export const [getFormatService, setFormatService] = createGetterSetter< - DataPublicPluginStart['fieldFormats'] ->('metric data.fieldFormats'); +export const [getFormatService, setFormatService] = + createGetterSetter('fieldFormats'); diff --git a/src/plugins/chart_expressions/expression_metric/public/index.ts b/src/plugins/chart_expressions/expression_metric/public/index.ts new file mode 100644 index 0000000000000..dfb442514d5f0 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/public/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { ExpressionMetricPlugin } from './plugin'; + +export function plugin() { + return new ExpressionMetricPlugin(); +} diff --git a/src/plugins/chart_expressions/expression_metric/public/plugin.ts b/src/plugins/chart_expressions/expression_metric/public/plugin.ts new file mode 100644 index 0000000000000..3ac338380a398 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/public/plugin.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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, CoreStart, Plugin } from '../../../../core/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../../expressions/public'; +import { metricVisFunction } from '../common'; +import { setFormatService } from './format_service'; +import { metricVisRenderer } from './expression_renderers'; +import { FieldFormatsStart } from '../../../field_formats/public'; + +/** @internal */ +export interface ExpressionMetricPluginSetup { + expressions: ReturnType; +} + +/** @internal */ +export interface ExpressionMetricPluginStart { + fieldFormats: FieldFormatsStart; +} + +/** @internal */ +export class ExpressionMetricPlugin implements Plugin { + public setup(core: CoreSetup, { expressions }: ExpressionMetricPluginSetup) { + expressions.registerFunction(metricVisFunction); + expressions.registerRenderer(metricVisRenderer); + } + + public start(core: CoreStart, { fieldFormats }: ExpressionMetricPluginStart) { + setFormatService(fieldFormats); + } +} diff --git a/src/plugins/chart_expressions/expression_metric/server/index.ts b/src/plugins/chart_expressions/expression_metric/server/index.ts new file mode 100644 index 0000000000000..dfb442514d5f0 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/server/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { ExpressionMetricPlugin } from './plugin'; + +export function plugin() { + return new ExpressionMetricPlugin(); +} diff --git a/src/plugins/chart_expressions/expression_metric/server/plugin.ts b/src/plugins/chart_expressions/expression_metric/server/plugin.ts new file mode 100644 index 0000000000000..1a04d4702361f --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/server/plugin.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Plugin } from '../../../../core/public'; +import { ExpressionsServerStart, ExpressionsServerSetup } from '../../../expressions/server'; +import { metricVisFunction } from '../common'; + +interface SetupDeps { + expressions: ExpressionsServerSetup; +} + +interface StartDeps { + expression: ExpressionsServerStart; +} + +export type ExpressionMetricPluginSetup = void; +export type ExpressionMetricPluginStart = void; + +export class ExpressionMetricPlugin + implements Plugin +{ + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionMetricPluginSetup { + expressions.registerFunction(metricVisFunction); + } + + public start(core: CoreStart): ExpressionMetricPluginStart {} + + public stop() {} +} diff --git a/src/plugins/chart_expressions/expression_metric/tsconfig.json b/src/plugins/chart_expressions/expression_metric/tsconfig.json new file mode 100644 index 0000000000000..ff5089c7f4d21 --- /dev/null +++ b/src/plugins/chart_expressions/expression_metric/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "isolatedModules": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + ], + "references": [ + { "path": "../../../core/tsconfig.json" }, + { "path": "../../expressions/tsconfig.json" }, + { "path": "../../presentation_util/tsconfig.json" }, + { "path": "../../field_formats/tsconfig.json" }, + { "path": "../../charts/tsconfig.json" }, + { "path": "../../visualizations/tsconfig.json" }, + ] +} diff --git a/src/plugins/charts/common/index.ts b/src/plugins/charts/common/index.ts index ad3d2d11bbdfd..618466212f5bb 100644 --- a/src/plugins/charts/common/index.ts +++ b/src/plugins/charts/common/index.ts @@ -6,9 +6,33 @@ * Side Public License, v 1. */ -// TODO: https://github.com/elastic/kibana/issues/110891 -/* eslint-disable @kbn/eslint/no_export_all */ - export const COLOR_MAPPING_SETTING = 'visualization:colorMapping'; -export * from './palette'; -export * from './constants'; + +export { + CustomPaletteArguments, + CustomPaletteState, + SystemPaletteArguments, + PaletteOutput, + defaultCustomColors, + palette, + systemPalette, +} from './palette'; + +export { paletteIds } from './constants'; + +export { + ColorSchemas, + ColorSchema, + RawColorSchema, + ColorMap, + vislibColorMaps, + colorSchemas, + getHeatmapColors, + truncatedColorMaps, + truncatedColorSchemas, + ColorMode, + LabelRotation, + defaultCountLabel, +} from './static'; + +export { ColorSchemaParams, Labels, Style } from './types'; diff --git a/src/plugins/charts/public/static/color_maps/color_maps.ts b/src/plugins/charts/common/static/color_maps/color_maps.ts similarity index 100% rename from src/plugins/charts/public/static/color_maps/color_maps.ts rename to src/plugins/charts/common/static/color_maps/color_maps.ts diff --git a/src/plugins/charts/public/static/color_maps/heatmap_color.test.ts b/src/plugins/charts/common/static/color_maps/heatmap_color.test.ts similarity index 100% rename from src/plugins/charts/public/static/color_maps/heatmap_color.test.ts rename to src/plugins/charts/common/static/color_maps/heatmap_color.test.ts diff --git a/src/plugins/charts/public/static/color_maps/heatmap_color.ts b/src/plugins/charts/common/static/color_maps/heatmap_color.ts similarity index 100% rename from src/plugins/charts/public/static/color_maps/heatmap_color.ts rename to src/plugins/charts/common/static/color_maps/heatmap_color.ts diff --git a/src/plugins/charts/public/static/color_maps/index.ts b/src/plugins/charts/common/static/color_maps/index.ts similarity index 100% rename from src/plugins/charts/public/static/color_maps/index.ts rename to src/plugins/charts/common/static/color_maps/index.ts diff --git a/src/plugins/charts/public/static/color_maps/mock.ts b/src/plugins/charts/common/static/color_maps/mock.ts similarity index 100% rename from src/plugins/charts/public/static/color_maps/mock.ts rename to src/plugins/charts/common/static/color_maps/mock.ts diff --git a/src/plugins/charts/public/static/color_maps/truncated_color_maps.ts b/src/plugins/charts/common/static/color_maps/truncated_color_maps.ts similarity index 100% rename from src/plugins/charts/public/static/color_maps/truncated_color_maps.ts rename to src/plugins/charts/common/static/color_maps/truncated_color_maps.ts diff --git a/src/plugins/charts/public/static/components/collections.ts b/src/plugins/charts/common/static/components/collections.ts similarity index 100% rename from src/plugins/charts/public/static/components/collections.ts rename to src/plugins/charts/common/static/components/collections.ts diff --git a/src/plugins/charts/common/static/components/index.ts b/src/plugins/charts/common/static/components/index.ts new file mode 100644 index 0000000000000..9b2384d237714 --- /dev/null +++ b/src/plugins/charts/common/static/components/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 { ColorMode, LabelRotation, defaultCountLabel } from './collections'; diff --git a/src/plugins/charts/common/static/index.ts b/src/plugins/charts/common/static/index.ts new file mode 100644 index 0000000000000..9cde3bafe59e4 --- /dev/null +++ b/src/plugins/charts/common/static/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { + ColorSchemas, + ColorSchema, + RawColorSchema, + ColorMap, + vislibColorMaps, + colorSchemas, + getHeatmapColors, + truncatedColorMaps, + truncatedColorSchemas, +} from './color_maps'; + +export { ColorMode, LabelRotation, defaultCountLabel } from './components'; diff --git a/src/plugins/charts/public/static/components/types.ts b/src/plugins/charts/common/types.ts similarity index 88% rename from src/plugins/charts/public/static/components/types.ts rename to src/plugins/charts/common/types.ts index a92dba593f0c7..841494c2edb8a 100644 --- a/src/plugins/charts/public/static/components/types.ts +++ b/src/plugins/charts/common/types.ts @@ -6,8 +6,7 @@ * Side Public License, v 1. */ -import { ColorSchemas } from '../color_maps'; -import { LabelRotation } from './collections'; +import { ColorSchemas, LabelRotation } from './static'; export interface ColorSchemaParams { colorSchema: ColorSchemas; diff --git a/src/plugins/charts/kibana.json b/src/plugins/charts/kibana.json index 86971d1018e0e..a3e0da41056d7 100644 --- a/src/plugins/charts/kibana.json +++ b/src/plugins/charts/kibana.json @@ -3,6 +3,7 @@ "version": "kibana", "server": true, "ui": true, + "extraPublicDirs": ["common"], "requiredPlugins": ["expressions"], "owner": { "name": "Vis Editors", diff --git a/src/plugins/charts/public/index.ts b/src/plugins/charts/public/index.ts index 6674b98fce910..e3d38b797c576 100644 --- a/src/plugins/charts/public/index.ts +++ b/src/plugins/charts/public/index.ts @@ -26,4 +26,19 @@ export { CustomPaletteState, SystemPaletteArguments, paletteIds, + ColorSchemas, + ColorSchema, + RawColorSchema, + ColorMap, + vislibColorMaps, + colorSchemas, + getHeatmapColors, + truncatedColorMaps, + truncatedColorSchemas, + ColorMode, + LabelRotation, + defaultCountLabel, + ColorSchemaParams, + Labels, + Style, } from '../common'; diff --git a/src/plugins/charts/public/mocks.ts b/src/plugins/charts/public/mocks.ts index 7460962c3e1fa..a481f8ca138ce 100644 --- a/src/plugins/charts/public/mocks.ts +++ b/src/plugins/charts/public/mocks.ts @@ -28,7 +28,7 @@ const createStartContract = (): Start => ({ palettes: paletteServiceMock.setup({} as any), }); -export { colorMapsMock } from './static/color_maps/mock'; +export { colorMapsMock } from '../common/static/color_maps/mock'; export const chartPluginMock = { createSetupContract, diff --git a/src/plugins/charts/public/static/components/index.ts b/src/plugins/charts/public/static/components/index.ts index 549218cf992aa..7f3af50a01aa4 100644 --- a/src/plugins/charts/public/static/components/index.ts +++ b/src/plugins/charts/public/static/components/index.ts @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -export { ColorMode, LabelRotation, defaultCountLabel } from './collections'; -export { ColorSchemaParams, Labels, Style } from './types'; export { LegendToggle } from './legend_toggle'; export { ColorPicker } from './color_picker'; export { CurrentTime } from './current_time'; diff --git a/src/plugins/charts/public/static/index.ts b/src/plugins/charts/public/static/index.ts index 068ac8289e008..6f5c87ce0df4d 100644 --- a/src/plugins/charts/public/static/index.ts +++ b/src/plugins/charts/public/static/index.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -export * from './color_maps'; export * from './colors'; export * from './components'; export * from './utils'; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index dedc390c47719..c01295f6ee42c 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -515,7 +515,6 @@ export const useField = ( if (resetValue) { hasBeenReset.current = true; const newValue = deserializeValue(updatedDefaultValue ?? defaultValue); - // updateStateIfMounted('value', newValue); setValue(newValue); return newValue; } diff --git a/src/plugins/vis_types/metric/kibana.json b/src/plugins/vis_types/metric/kibana.json index 950abea1c5a9b..ab69f78430338 100644 --- a/src/plugins/vis_types/metric/kibana.json +++ b/src/plugins/vis_types/metric/kibana.json @@ -5,7 +5,7 @@ "server": true, "ui": true, "requiredPlugins": ["data", "visualizations", "charts", "expressions"], - "requiredBundles": ["kibanaUtils", "visDefaultEditor"], + "requiredBundles": ["visDefaultEditor"], "owner": { "name": "Vis Editors", "githubTeam": "kibana-vis-editors" diff --git a/src/plugins/vis_types/metric/public/components/index.ts b/src/plugins/vis_types/metric/public/components/index.ts new file mode 100644 index 0000000000000..41c9bf10e7387 --- /dev/null +++ b/src/plugins/vis_types/metric/public/components/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 { MetricVisOptions } from './metric_vis_options'; diff --git a/src/plugins/vis_types/metric/public/metric_vis_type.ts b/src/plugins/vis_types/metric/public/metric_vis_type.ts index 9fc3856ba0edf..ccacd4eae6bf9 100644 --- a/src/plugins/vis_types/metric/public/metric_vis_type.ts +++ b/src/plugins/vis_types/metric/public/metric_vis_type.ts @@ -7,7 +7,7 @@ */ import { i18n } from '@kbn/i18n'; -import { MetricVisOptions } from './components/metric_vis_options'; +import { MetricVisOptions } from './components'; import { ColorSchemas, ColorMode } from '../../../charts/public'; import { VisTypeDefinition } from '../../../visualizations/public'; import { AggGroupNames } from '../../../data/public'; diff --git a/src/plugins/vis_types/metric/public/plugin.ts b/src/plugins/vis_types/metric/public/plugin.ts index 205c02d8e9c3b..a88bbc8e5f2e9 100644 --- a/src/plugins/vis_types/metric/public/plugin.ts +++ b/src/plugins/vis_types/metric/public/plugin.ts @@ -7,27 +7,13 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../../expressions/public'; import { VisualizationsSetup } from '../../../visualizations/public'; - -import { createMetricVisFn } from './metric_vis_fn'; import { createMetricVisTypeDefinition } from './metric_vis_type'; -import { ChartsPluginSetup } from '../../../charts/public'; -import { DataPublicPluginStart } from '../../../data/public'; -import { setFormatService } from './services'; import { ConfigSchema } from '../config'; -import { metricVisRenderer } from './metric_vis_renderer'; /** @internal */ export interface MetricVisPluginSetupDependencies { - expressions: ReturnType; visualizations: VisualizationsSetup; - charts: ChartsPluginSetup; -} - -/** @internal */ -export interface MetricVisPluginStartDependencies { - data: DataPublicPluginStart; } /** @internal */ @@ -38,16 +24,9 @@ export class MetricVisPlugin implements Plugin { this.initializerContext = initializerContext; } - public setup( - core: CoreSetup, - { expressions, visualizations, charts }: MetricVisPluginSetupDependencies - ) { - expressions.registerFunction(createMetricVisFn); - expressions.registerRenderer(metricVisRenderer); + public setup(core: CoreSetup, { visualizations }: MetricVisPluginSetupDependencies) { visualizations.createBaseVisualization(createMetricVisTypeDefinition()); } - public start(core: CoreStart, { data }: MetricVisPluginStartDependencies) { - setFormatService(data.fieldFormats); - } + public start(core: CoreStart) {} } diff --git a/src/plugins/vis_types/metric/public/to_ast.ts b/src/plugins/vis_types/metric/public/to_ast.ts index 10c782c9a50fb..1e23a10dd7608 100644 --- a/src/plugins/vis_types/metric/public/to_ast.ts +++ b/src/plugins/vis_types/metric/public/to_ast.ts @@ -9,7 +9,6 @@ import { get } from 'lodash'; import { getVisSchemas, SchemaConfig, VisToExpressionAst } from '../../../visualizations/public'; import { buildExpression, buildExpressionFunction } from '../../../expressions/public'; -import { MetricVisExpressionFunctionDefinition } from './metric_vis_fn'; import { EsaggsExpressionFunctionDefinition, IndexPatternLoadExpressionFunctionDefinition, @@ -63,8 +62,7 @@ export const toExpressionAst: VisToExpressionAst = (vis, params) => { }); } - // @ts-expect-error - const metricVis = buildExpressionFunction('metricVis', { + const metricVis = buildExpressionFunction('metricVis', { percentageMode, colorSchema, colorMode: metricColorMode, diff --git a/src/plugins/vis_types/metric/public/types.ts b/src/plugins/vis_types/metric/public/types.ts index 8e86c0217bba6..98065348fa91d 100644 --- a/src/plugins/vis_types/metric/public/types.ts +++ b/src/plugins/vis_types/metric/public/types.ts @@ -36,12 +36,3 @@ export interface VisParams { metric: MetricVisParam; type: typeof visType; } - -export interface MetricVisMetric { - value: any; - label: string; - color?: string; - bgColor?: string; - lightText: boolean; - rowIndex: number; -} diff --git a/src/plugins/vis_types/metric/tsconfig.json b/src/plugins/vis_types/metric/tsconfig.json index e8c878425ff70..e8e2bb0573014 100644 --- a/src/plugins/vis_types/metric/tsconfig.json +++ b/src/plugins/vis_types/metric/tsconfig.json @@ -13,8 +13,6 @@ { "path": "../../visualizations/tsconfig.json" }, { "path": "../../charts/tsconfig.json" }, { "path": "../../expressions/tsconfig.json" }, - { "path": "../../kibana_utils/tsconfig.json" }, - { "path": "../../vis_default_editor/tsconfig.json" }, - { "path": "../../field_formats/tsconfig.json" } + { "path": "../../vis_default_editor/tsconfig.json" } ] } diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx b/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx index d28e2c5e0fb9a..1029ac67cc43c 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx @@ -33,28 +33,22 @@ export interface IndexPatternSelectProps { | null; } -const queryAllIndicesHelpText = ( - *, - }} - /> +const defaultIndexPatternHelpText = i18n.translate( + 'visTypeTimeseries.indexPatternSelect.defaultIndexPatternText', + { + defaultMessage: 'Default index pattern is used.', + } ); -const getIndexPatternHelpText = (useKibanaIndices: boolean) => ( - +const queryAllIndexesHelpText = i18n.translate( + 'visTypeTimeseries.indexPatternSelect.queryAllIndexesText', + { + defaultMessage: 'To query all indexes use *', + } ); const indexPatternLabel = i18n.translate('visTypeTimeseries.indexPatternSelect.label', { - defaultMessage: 'Data view', + defaultMessage: 'Index pattern', }); export const IndexPatternSelect = ({ @@ -109,14 +103,17 @@ export const IndexPatternSelect = ({ diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx b/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx index 16eeaff30c208..f33f51f60d048 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx @@ -79,7 +79,7 @@ export const SwitchModePopover = ({ onModeChange, useKibanaIndices }: PopoverPro aria-label={i18n.translate( 'visTypeTimeseries.indexPatternSelect.switchModePopover.areaLabel', { - defaultMessage: 'Configure data view selection mode', + defaultMessage: 'Configure index pattern selection mode', } )} onClick={onButtonClick} @@ -97,13 +97,14 @@ export const SwitchModePopover = ({ onModeChange, useKibanaIndices }: PopoverPro > {i18n.translate('visTypeTimeseries.indexPatternSelect.switchModePopover.title', { - defaultMessage: 'Data view mode', + defaultMessage: 'Index pattern selection mode', })} { { } iconType="cheer" @@ -42,13 +42,13 @@ export const UseIndexPatternModeCallout = () => { >

@@ -59,7 +59,7 @@ export const UseIndexPatternModeCallout = () => { diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js index 208f9af9bb250..4257c35a6d4c2 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js @@ -538,8 +538,8 @@ export const TimeseriesConfig = injectI18n(function (props) { & { id: string }, - savedVisualizationsLoader?: SavedVisualizationsLoader, attributeService?: AttributeService< VisualizeSavedObjectAttributes, VisualizeByValueInput, @@ -46,16 +39,12 @@ export const createVisEmbeddableFromObject = >, parent?: IContainer ): Promise => { - const savedVisualizations = getSavedVisualizationsLoader(); - try { const visId = vis.id as string; - const editPath = visId ? savedVisualizations.urlFor(visId) : '#/edit_by_value'; + const editPath = visId ? urlFor(visId) : '#/edit_by_value'; - const editUrl = visId - ? getHttp().basePath.prepend(`/app/visualize${savedVisualizations.urlFor(visId)}`) - : ''; + const editUrl = visId ? getHttp().basePath.prepend(`/app/visualize${urlFor(visId)}`) : ''; const isLabsEnabled = getUISettings().get(VISUALIZE_ENABLE_LABS_SETTING); if (!isLabsEnabled && vis.type.stage === 'experimental') { @@ -87,7 +76,6 @@ export const createVisEmbeddableFromObject = }, input, attributeService, - savedVisualizationsLoader, parent ); } catch (e) { diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index f5a7349b633eb..0c7d58453db69 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -39,7 +39,7 @@ import { getExpressions, getUiActions } from '../services'; import { VIS_EVENT_TO_TRIGGER } from './events'; import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory'; import { SavedObjectAttributes } from '../../../../core/types'; -import { SavedVisualizationsLoader } from '../saved_visualizations'; +import { getSavedVisualization } from '../utils/saved_visualize_utils'; import { VisSavedObject } from '../types'; import { toExpressionAst } from './to_ast'; @@ -108,7 +108,6 @@ export class VisualizeEmbeddable VisualizeByValueInput, VisualizeByReferenceInput >; - private savedVisualizationsLoader?: SavedVisualizationsLoader; constructor( timefilter: TimefilterContract, @@ -119,7 +118,6 @@ export class VisualizeEmbeddable VisualizeByValueInput, VisualizeByReferenceInput >, - savedVisualizationsLoader?: SavedVisualizationsLoader, parent?: IContainer ) { super( @@ -144,7 +142,6 @@ export class VisualizeEmbeddable this.vis.uiState.on('change', this.uiStateChangeHandler); this.vis.uiState.on('reload', this.reload); this.attributeService = attributeService; - this.savedVisualizationsLoader = savedVisualizationsLoader; if (this.attributeService) { const isByValue = !this.inputIsRefType(initialInput); @@ -455,7 +452,15 @@ export class VisualizeEmbeddable }; getInputAsRefType = async (): Promise => { - const savedVis = await this.savedVisualizationsLoader?.get({}); + const { savedObjectsClient, data, spaces, savedObjectsTaggingOss } = await this.deps.start() + .plugins; + const savedVis = await getSavedVisualization({ + savedObjectsClient, + search: data.search, + dataViews: data.dataViews, + spaces, + savedObjectsTagging: savedObjectsTaggingOss?.getTaggingApi(), + }); if (!savedVis) { throw new Error('Error creating a saved vis object'); } diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index 9e22b33bdee9d..9b1af5bea3fce 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -33,20 +33,20 @@ import type { import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import type { SerializedVis, Vis } from '../vis'; import { createVisAsync } from '../vis_async'; -import { - getCapabilities, - getTypes, - getUISettings, - getSavedVisualizationsLoader, -} from '../services'; +import { getCapabilities, getTypes, getUISettings } from '../services'; import { showNewVisModal } from '../wizard'; -import { convertToSerializedVis } from '../saved_visualizations/_saved_vis'; +import { + convertToSerializedVis, + getSavedVisualization, + saveVisualization, + getFullPath, +} from '../utils/saved_visualize_utils'; import { extractControlsReferences, extractTimeSeriesReferences, injectTimeSeriesReferences, injectControlsReferences, -} from '../saved_visualizations/saved_visualization_references'; +} from '../utils/saved_visualization_references'; import { createVisEmbeddableFromObject } from './create_vis_embeddable_from_object'; import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants'; import { checkForDuplicateTitle } from '../../../saved_objects/public'; @@ -59,7 +59,15 @@ interface VisualizationAttributes extends SavedObjectAttributes { export interface VisualizeEmbeddableFactoryDeps { start: StartServicesGetter< - Pick + Pick< + VisualizationsStartDeps, + | 'inspector' + | 'embeddable' + | 'savedObjectsClient' + | 'data' + | 'savedObjectsTaggingOss' + | 'spaces' + > >; } @@ -147,17 +155,36 @@ export class VisualizeEmbeddableFactory input: Partial & { id: string }, parent?: IContainer ): Promise { - const savedVisualizations = getSavedVisualizationsLoader(); + const startDeps = await this.deps.start(); try { - const savedObject = await savedVisualizations.get(savedObjectId); + const savedObject = await getSavedVisualization( + { + savedObjectsClient: startDeps.core.savedObjects.client, + search: startDeps.plugins.data.search, + dataViews: startDeps.plugins.data.dataViews, + spaces: startDeps.plugins.spaces, + savedObjectsTagging: startDeps.plugins.savedObjectsTaggingOss?.getTaggingApi(), + }, + savedObjectId + ); + + if (savedObject.sharingSavedObjectProps?.outcome === 'conflict') { + return new ErrorEmbeddable( + i18n.translate('visualizations.embeddable.legacyURLConflict.errorMessage', { + defaultMessage: `This visualization has the same URL as a legacy alias. Disable the alias to resolve this error : {json}`, + values: { json: savedObject.sharingSavedObjectProps?.errorJSON }, + }), + input, + parent + ); + } const visState = convertToSerializedVis(savedObject); const vis = await createVisAsync(savedObject.visState.type, visState); return createVisEmbeddableFromObject(this.deps)( vis, input, - savedVisualizations, await this.getAttributeService(), parent ); @@ -173,11 +200,9 @@ export class VisualizeEmbeddableFactory if (input.savedVis) { const visState = input.savedVis; const vis = await createVisAsync(visState.type, visState); - const savedVisualizations = getSavedVisualizationsLoader(); return createVisEmbeddableFromObject(this.deps)( vis, input, - savedVisualizations, await this.getAttributeService(), parent ); @@ -201,9 +226,9 @@ export class VisualizeEmbeddableFactory confirmOverwrite: false, returnToOrigin: true, isTitleDuplicateConfirmed: true, + copyOnSave: false, }; savedVis.title = title; - savedVis.copyOnSave = false; savedVis.description = ''; savedVis.searchSourceFields = visObj?.data.searchSource?.getSerializedFields(); const serializedVis = (visObj as unknown as Vis).serialize(); @@ -217,7 +242,12 @@ export class VisualizeEmbeddableFactory if (visObj) { savedVis.uiStateJSON = visObj?.uiState.toString(); } - const id = await savedVis.save(saveOptions); + const { core, plugins } = await this.deps.start(); + const id = await saveVisualization(savedVis, saveOptions, { + savedObjectsClient: core.savedObjects.client, + overlays: core.overlays, + savedObjectsTagging: plugins.savedObjectsTaggingOss?.getTaggingApi(), + }); if (!id || id === '') { throw new Error( i18n.translate('visualizations.savingVisualizationFailed.errorMsg', { @@ -225,6 +255,7 @@ export class VisualizeEmbeddableFactory }) ); } + core.chrome.recentlyAccessed.add(getFullPath(id), savedVis.title, String(id)); return { id }; } catch (error) { throw error; diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index 0886f230d101f..e6ea3cd489556 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -38,6 +38,7 @@ export { VisToExpressionAst, VisToExpressionAstParams, VisEditorOptionsProps, + GetVisOptions, } from './types'; export { VisualizationListItem, VisualizationStage } from './vis_types/vis_type_alias_registry'; export { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants'; @@ -49,3 +50,4 @@ export { FakeParams, HistogramParams, } from '../common/expression_functions/xy_dimension'; +export { urlFor, getFullPath } from './utils/saved_visualize_utils'; diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index 901593626a945..9b2d6bfe25b32 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -10,6 +10,7 @@ import { PluginInitializerContext } from '../../../core/public'; import { Schema, VisualizationsSetup, VisualizationsStart } from './'; import { Schemas } from './vis_types'; import { VisualizationsPlugin } from './plugin'; +import { spacesPluginMock } from '../../../../x-pack/plugins/spaces/public/mocks'; import { coreMock, applicationServiceMock } from '../../../core/public/mocks'; import { embeddablePluginMock } from '../../../plugins/embeddable/public/mocks'; import { expressionsPluginMock } from '../../../plugins/expressions/public/mocks'; @@ -18,6 +19,7 @@ import { usageCollectionPluginMock } from '../../../plugins/usage_collection/pub import { uiActionsPluginMock } from '../../../plugins/ui_actions/public/mocks'; import { inspectorPluginMock } from '../../../plugins/inspector/public/mocks'; import { savedObjectsPluginMock } from '../../../plugins/saved_objects/public/mocks'; +import { savedObjectTaggingOssPluginMock } from '../../saved_objects_tagging_oss/public/mocks'; const createSetupContract = (): VisualizationsSetup => ({ createBaseVisualization: jest.fn(), @@ -34,6 +36,9 @@ const createStartContract = (): VisualizationsStart => ({ savedVisualizationsLoader: { get: jest.fn(), } as any, + getSavedVisualization: jest.fn(), + saveVisualization: jest.fn(), + findListItems: jest.fn(), showNewVisModal: jest.fn(), createVis: jest.fn(), convertFromSerializedVis: jest.fn(), @@ -61,9 +66,11 @@ const createInstance = async () => { uiActions: uiActionsPluginMock.createStartContract(), application: applicationServiceMock.createStartContract(), embeddable: embeddablePluginMock.createStartContract(), + spaces: spacesPluginMock.createStartContract(), getAttributeService: jest.fn(), savedObjectsClient: coreMock.createStart().savedObjects.client, savedObjects: savedObjectsPluginMock.createStartContract(), + savedObjectsTaggingOss: savedObjectTaggingOssPluginMock.createStart(), }); return { diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index ee3e914aa4bc6..47f544ce2f5d3 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -5,6 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + +import type { SavedObjectsFindOptionsReference } from 'kibana/public'; import { setUISettings, setTypes, @@ -30,6 +32,7 @@ import { VisualizeEmbeddableFactory, createVisEmbeddableFromObject, } from './embeddable'; +import type { SpacesPluginStart } from '../../../../x-pack/plugins/spaces/public'; import { TypesService } from './vis_types/types_service'; import { range as rangeExpressionFunction } from '../common/expression_functions/range'; import { visDimension as visDimensionExpressionFunction } from '../common/expression_functions/vis_dimension'; @@ -43,7 +46,10 @@ import { showNewVisModal } from './wizard'; import { convertFromSerializedVis, convertToSerializedVis, -} from './saved_visualizations/_saved_vis'; + getSavedVisualization, + saveVisualization, + findListItems, +} from './utils/saved_visualize_utils'; import { createSavedSearchesLoader } from '../../discover/public'; @@ -66,7 +72,9 @@ import type { import type { DataPublicPluginSetup, DataPublicPluginStart } from '../../../plugins/data/public'; import type { ExpressionsSetup, ExpressionsStart } from '../../expressions/public'; import type { EmbeddableSetup, EmbeddableStart } from '../../embeddable/public'; +import type { SavedObjectTaggingOssPluginStart } from '../../saved_objects_tagging_oss/public'; import { createVisAsync } from './vis_async'; +import type { VisSavedObject, SaveVisOptions, GetVisOptions } from './types'; /** * Interface for this plugin's returned setup/start contracts. @@ -82,6 +90,13 @@ export interface VisualizationsStart extends TypesStart { convertToSerializedVis: typeof convertToSerializedVis; convertFromSerializedVis: typeof convertFromSerializedVis; showNewVisModal: typeof showNewVisModal; + getSavedVisualization: (opts?: GetVisOptions | string) => Promise; + saveVisualization: (savedVis: VisSavedObject, saveOptions: SaveVisOptions) => Promise; + findListItems: ( + searchTerm: string, + listingLimit: number, + references?: SavedObjectsFindOptionsReference[] + ) => Promise<{ hits: Array>; total: number }>; __LEGACY: { createVisEmbeddableFromObject: ReturnType }; } @@ -103,6 +118,8 @@ export interface VisualizationsStartDeps { getAttributeService: EmbeddableStart['getAttributeService']; savedObjects: SavedObjectsStart; savedObjectsClient: SavedObjectsClientContract; + spaces?: SpacesPluginStart; + savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; } /** @@ -149,7 +166,15 @@ export class VisualizationsPlugin public start( core: CoreStart, - { data, expressions, uiActions, embeddable, savedObjects }: VisualizationsStartDeps + { + data, + expressions, + uiActions, + embeddable, + savedObjects, + spaces, + savedObjectsTaggingOss, + }: VisualizationsStartDeps ): VisualizationsStart { const types = this.types.start(); setTypes(types); @@ -181,6 +206,28 @@ export class VisualizationsPlugin return { ...types, showNewVisModal, + getSavedVisualization: async (opts) => { + return getSavedVisualization( + { + search: data.search, + savedObjectsClient: core.savedObjects.client, + dataViews: data.dataViews, + spaces, + savedObjectsTagging: savedObjectsTaggingOss?.getTaggingApi(), + }, + opts + ); + }, + saveVisualization: async (savedVis, saveOptions) => { + return saveVisualization(savedVis, saveOptions, { + savedObjectsClient: core.savedObjects.client, + overlays: core.overlays, + savedObjectsTagging: savedObjectsTaggingOss?.getTaggingApi(), + }); + }, + findListItems: async (searchTerm, listingLimit, references) => { + return findListItems(core.savedObjects.client, types, searchTerm, listingLimit, references); + }, /** * creates new instance of Vis * @param {IndexPattern} indexPattern - index pattern to use diff --git a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts index fb6c99ac8ef02..fbd8e414c2738 100644 --- a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts +++ b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts @@ -16,11 +16,11 @@ import type { SavedObjectsStart, SavedObject } from '../../../../plugins/saved_objects/public'; // @ts-ignore import { updateOldState } from '../legacy/vis_update_state'; -import { extractReferences, injectReferences } from './saved_visualization_references'; +import { extractReferences, injectReferences } from '../utils/saved_visualization_references'; import { createSavedSearchesLoader } from '../../../discover/public'; import type { SavedObjectsClientContract } from '../../../../core/public'; import type { IndexPatternsContract } from '../../../../plugins/data/public'; -import type { ISavedVis, SerializedVis } from '../types'; +import type { ISavedVis } from '../types'; export interface SavedVisServices { savedObjectsClient: SavedObjectsClientContract; @@ -28,43 +28,7 @@ export interface SavedVisServices { indexPatterns: IndexPatternsContract; } -export const convertToSerializedVis = (savedVis: ISavedVis): SerializedVis => { - const { id, title, description, visState, uiStateJSON, searchSourceFields } = savedVis; - - const aggs = searchSourceFields && searchSourceFields.index ? visState.aggs || [] : visState.aggs; - - return { - id, - title, - type: visState.type, - description, - params: visState.params, - uiState: JSON.parse(uiStateJSON || '{}'), - data: { - aggs, - searchSource: searchSourceFields!, - savedSearchId: savedVis.savedSearchId, - }, - }; -}; - -export const convertFromSerializedVis = (vis: SerializedVis): ISavedVis => { - return { - id: vis.id, - title: vis.title, - description: vis.description, - visState: { - title: vis.title, - type: vis.type, - aggs: vis.data.aggs, - params: vis.params, - }, - uiStateJSON: JSON.stringify(vis.uiState), - searchSourceFields: vis.data.searchSource, - savedSearchId: vis.data.savedSearchId, - }; -}; - +/** @deprecated **/ export function createSavedVisClass(services: SavedVisServices) { const savedSearch = createSavedSearchesLoader(services); diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts index d07d28b393dcc..cec65b8f988b3 100644 --- a/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts +++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts @@ -22,6 +22,7 @@ export interface FindListItemsOptions { references?: SavedObjectsFindOptionsReference[]; } +/** @deprecated **/ export function createSavedVisLoader(services: SavedVisServicesWithVisualizations) { const { savedObjectsClient, visualizationTypes } = services; diff --git a/src/plugins/visualizations/public/types.ts b/src/plugins/visualizations/public/types.ts index d68599c0724f6..5be8f49e9cdc7 100644 --- a/src/plugins/visualizations/public/types.ts +++ b/src/plugins/visualizations/public/types.ts @@ -6,13 +6,14 @@ * Side Public License, v 1. */ -import { SavedObject } from '../../../plugins/saved_objects/public'; +import type { SavedObjectsMigrationVersion } from 'kibana/public'; import { IAggConfigs, SearchSourceFields, TimefilterContract, AggConfigSerialized, } from '../../../plugins/data/public'; +import type { ISearchSource } from '../../data/common'; import { ExpressionAstExpression } from '../../expressions/public'; import type { SerializedVis, Vis } from './vis'; @@ -36,9 +37,39 @@ export interface ISavedVis { uiStateJSON?: string; savedSearchRefName?: string; savedSearchId?: string; + sharingSavedObjectProps?: { + outcome?: 'aliasMatch' | 'exactMatch' | 'conflict'; + aliasTargetId?: string; + errorJSON?: string; + }; } -export interface VisSavedObject extends SavedObject, ISavedVis {} +export interface VisSavedObject extends ISavedVis { + lastSavedTitle: string; + getEsType: () => string; + getDisplayName?: () => string; + displayName: string; + migrationVersion?: SavedObjectsMigrationVersion; + searchSource?: ISearchSource; + version?: string; + tags?: string[]; +} + +export interface SaveVisOptions { + confirmOverwrite?: boolean; + isTitleDuplicateConfirmed?: boolean; + onTitleDuplicate?: () => void; + copyOnSave?: boolean; +} + +export interface GetVisOptions { + id?: string; + searchSource?: boolean; + migrationVersion?: SavedObjectsMigrationVersion; + savedSearchId?: string; + type?: string; + indexPattern?: string; +} export interface VisToExpressionAstParams { timefilter: TimefilterContract; diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts b/src/plugins/visualizations/public/utils/saved_visualization_references/controls_references.ts similarity index 100% rename from src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts rename to src/plugins/visualizations/public/utils/saved_visualization_references/controls_references.ts diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/index.ts b/src/plugins/visualizations/public/utils/saved_visualization_references/index.ts similarity index 100% rename from src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/index.ts rename to src/plugins/visualizations/public/utils/saved_visualization_references/index.ts diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.test.ts b/src/plugins/visualizations/public/utils/saved_visualization_references/saved_visualization_references.test.ts similarity index 100% rename from src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.test.ts rename to src/plugins/visualizations/public/utils/saved_visualization_references/saved_visualization_references.test.ts diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.ts b/src/plugins/visualizations/public/utils/saved_visualization_references/saved_visualization_references.ts similarity index 100% rename from src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.ts rename to src/plugins/visualizations/public/utils/saved_visualization_references/saved_visualization_references.ts diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts b/src/plugins/visualizations/public/utils/saved_visualization_references/timeseries_references.ts similarity index 100% rename from src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts rename to src/plugins/visualizations/public/utils/saved_visualization_references/timeseries_references.ts diff --git a/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts b/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts new file mode 100644 index 0000000000000..83b16026de391 --- /dev/null +++ b/src/plugins/visualizations/public/utils/saved_visualize_utils.test.ts @@ -0,0 +1,507 @@ +/* + * Copyright 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 { ISearchSource } from '../../../data/common'; +import type { SpacesPluginStart } from '../../../../../x-pack/plugins/spaces/public'; +import type { SavedObjectsTaggingApi } from '../../../saved_objects_tagging_oss/public'; +import { coreMock } from '../../../../core/public/mocks'; +import { dataPluginMock } from '../../../data/public/mocks'; +import { SavedObjectsClientContract } from '../../../../core/public'; +import { + findListItems, + getSavedVisualization, + saveVisualization, + SAVED_VIS_TYPE, +} from './saved_visualize_utils'; +import { VisTypeAlias, TypesStart } from '../vis_types'; +import type { VisSavedObject } from '../types'; + +let visTypes = [] as VisTypeAlias[]; +const mockGetAliases = jest.fn(() => visTypes); +const mockGetTypes = jest.fn((type: string) => type) as unknown as TypesStart['get']; +jest.mock('../services', () => ({ + getSpaces: jest.fn(() => ({ + getActiveSpace: () => ({ + id: 'test', + }), + })), +})); + +const mockParseSearchSourceJSON = jest.fn(); +const mockInjectSearchSourceReferences = jest.fn(); +const mockExtractSearchSourceReferences = jest.fn((...args) => [{}, []]); + +jest.mock('../../../../plugins/data/public', () => ({ + extractSearchSourceReferences: jest.fn((...args) => mockExtractSearchSourceReferences(...args)), + injectSearchSourceReferences: jest.fn((...args) => mockInjectSearchSourceReferences(...args)), + parseSearchSourceJSON: jest.fn((...args) => mockParseSearchSourceJSON(...args)), +})); + +const mockInjectReferences = jest.fn(); +const mockExtractReferences = jest.fn(() => ({ references: [], attributes: {} })); +jest.mock('./saved_visualization_references', () => ({ + injectReferences: jest.fn((...args) => mockInjectReferences(...args)), + extractReferences: jest.fn(() => mockExtractReferences()), +})); + +let isTitleDuplicateConfirmed = true; +const mockCheckForDuplicateTitle = jest.fn(() => { + if (!isTitleDuplicateConfirmed) { + throw new Error(); + } +}); +const mockSaveWithConfirmation = jest.fn(() => ({ id: 'test-after-confirm' })); +jest.mock('../../../../plugins/saved_objects/public', () => ({ + checkForDuplicateTitle: jest.fn(() => mockCheckForDuplicateTitle()), + saveWithConfirmation: jest.fn(() => mockSaveWithConfirmation()), + isErrorNonFatal: jest.fn(() => true), +})); + +describe('saved_visualize_utils', () => { + const { overlays, savedObjects } = coreMock.createStart(); + const savedObjectsClient = savedObjects.client as jest.Mocked; + (savedObjectsClient.resolve as jest.Mock).mockImplementation(() => ({ + saved_object: { + references: [ + { + id: 'test', + type: 'index-pattern', + }, + ], + attributes: { + visState: JSON.stringify({ type: 'area' }), + kibanaSavedObjectMeta: { + searchSourceJSON: '{filter: []}', + }, + }, + _version: '1', + }, + outcome: 'exact', + alias_target_id: null, + })); + (savedObjectsClient.create as jest.Mock).mockImplementation(() => ({ id: 'test' })); + const { dataViews, search } = dataPluginMock.createStartContract(); + + describe('getSavedVisualization', () => { + beforeEach(() => { + mockParseSearchSourceJSON.mockClear(); + mockInjectSearchSourceReferences.mockClear(); + mockInjectReferences.mockClear(); + }); + it('should return object with defaults if was not provided id', async () => { + const savedVis = await getSavedVisualization({ + savedObjectsClient, + search, + dataViews, + spaces: Promise.resolve({ + getActiveSpace: () => ({ + id: 'test', + }), + }) as unknown as SpacesPluginStart, + }); + expect(savedVis).toBeDefined(); + expect(savedVis.title).toBe(''); + expect(savedVis.displayName).toBe(SAVED_VIS_TYPE); + }); + + it('should create search source if saved object has searchSourceJSON', async () => { + await getSavedVisualization( + { + savedObjectsClient, + search, + dataViews, + spaces: Promise.resolve({ + getActiveSpace: () => ({ + id: 'test', + }), + }) as unknown as SpacesPluginStart, + }, + { id: 'test', searchSource: true } + ); + expect(mockParseSearchSourceJSON).toHaveBeenCalledWith('{filter: []}'); + expect(mockInjectSearchSourceReferences).toHaveBeenCalled(); + expect(search.searchSource.create).toHaveBeenCalled(); + }); + + it('should inject references if saved object has references', async () => { + await getSavedVisualization( + { + savedObjectsClient, + search, + dataViews, + spaces: Promise.resolve({ + getActiveSpace: () => ({ + id: 'test', + }), + }) as unknown as SpacesPluginStart, + }, + { id: 'test', searchSource: true } + ); + expect(mockInjectReferences.mock.calls[0][1]).toEqual([ + { + id: 'test', + type: 'index-pattern', + }, + ]); + }); + + it('should call getTagIdsFromReferences if we provide savedObjectsTagging service', async () => { + const mockGetTagIdsFromReferences = jest.fn(() => ['test']); + await getSavedVisualization( + { + savedObjectsClient, + search, + dataViews, + spaces: Promise.resolve({ + getActiveSpace: () => ({ + id: 'test', + }), + }) as unknown as SpacesPluginStart, + savedObjectsTagging: { + ui: { + getTagIdsFromReferences: mockGetTagIdsFromReferences, + }, + } as unknown as SavedObjectsTaggingApi, + }, + { id: 'test', searchSource: true } + ); + expect(mockGetTagIdsFromReferences).toHaveBeenCalled(); + }); + }); + + describe('saveVisualization', () => { + let vis: VisSavedObject; + beforeEach(() => { + mockExtractSearchSourceReferences.mockClear(); + mockExtractReferences.mockClear(); + mockSaveWithConfirmation.mockClear(); + savedObjectsClient.create.mockClear(); + vis = { + visState: { + type: 'area', + }, + title: 'test', + uiStateJSON: '{}', + version: '1', + __tags: [], + lastSavedTitle: 'test', + displayName: 'test', + getEsType: () => 'vis', + } as unknown as VisSavedObject; + }); + + it('should return id after save', async () => { + const savedVisId = await saveVisualization(vis, {}, { savedObjectsClient, overlays }); + expect(savedObjectsClient.create).toHaveBeenCalled(); + expect(mockExtractReferences).toHaveBeenCalled(); + expect(savedVisId).toBe('test'); + }); + + it('should call extractSearchSourceReferences if we new vis has searchSourceFields', async () => { + vis.searchSourceFields = { fields: [] }; + await saveVisualization(vis, {}, { savedObjectsClient, overlays }); + expect(mockExtractSearchSourceReferences).toHaveBeenCalledWith(vis.searchSourceFields); + }); + + it('should serialize searchSource', async () => { + vis.searchSource = { + serialize: jest.fn(() => ({ searchSourceJSON: '{}', references: [] })), + } as unknown as ISearchSource; + await saveVisualization(vis, {}, { savedObjectsClient, overlays }); + expect(vis.searchSource?.serialize).toHaveBeenCalled(); + }); + + it('should call updateTagsReferences if we provide savedObjectsTagging service', async () => { + const mockUpdateTagsReferences = jest.fn(() => []); + await saveVisualization( + vis, + {}, + { + savedObjectsClient, + overlays, + savedObjectsTagging: { + ui: { + updateTagsReferences: mockUpdateTagsReferences, + }, + } as unknown as SavedObjectsTaggingApi, + } + ); + expect(mockUpdateTagsReferences).toHaveBeenCalled(); + }); + + describe('confirmOverwrite', () => { + it('as false we should not call saveWithConfirmation and just do create', async () => { + const savedVisId = await saveVisualization( + vis, + { confirmOverwrite: false }, + { savedObjectsClient, overlays } + ); + expect(savedObjectsClient.create).toHaveBeenCalled(); + expect(mockExtractReferences).toHaveBeenCalled(); + expect(mockSaveWithConfirmation).not.toHaveBeenCalled(); + expect(savedVisId).toBe('test'); + }); + + it('as true we should call saveWithConfirmation', async () => { + const savedVisId = await saveVisualization( + vis, + { confirmOverwrite: true }, + { savedObjectsClient, overlays } + ); + expect(savedObjectsClient.create).not.toHaveBeenCalled(); + expect(mockSaveWithConfirmation).toHaveBeenCalled(); + expect(savedVisId).toBe('test-after-confirm'); + }); + }); + + describe('isTitleDuplicateConfirmed', () => { + it('as false we should not save vis with duplicated title', async () => { + isTitleDuplicateConfirmed = false; + const savedVisId = await saveVisualization( + vis, + { isTitleDuplicateConfirmed }, + { savedObjectsClient, overlays } + ); + expect(savedObjectsClient.create).not.toHaveBeenCalled(); + expect(mockSaveWithConfirmation).not.toHaveBeenCalled(); + expect(mockCheckForDuplicateTitle).toHaveBeenCalled(); + expect(savedVisId).toBe(''); + expect(vis.id).toBeUndefined(); + }); + + it('as true we should save vis with duplicated title', async () => { + isTitleDuplicateConfirmed = true; + const savedVisId = await saveVisualization( + vis, + { isTitleDuplicateConfirmed }, + { savedObjectsClient, overlays } + ); + expect(mockCheckForDuplicateTitle).toHaveBeenCalled(); + expect(savedObjectsClient.create).toHaveBeenCalled(); + expect(savedVisId).toBe('test'); + expect(vis.id).toBe('test'); + }); + }); + }); + + describe('findListItems', () => { + function testProps() { + (savedObjectsClient.find as jest.Mock).mockImplementation(() => ({ + total: 0, + savedObjects: [], + })); + return { + savedObjectsClient, + search: '', + size: 10, + }; + } + + beforeEach(() => { + savedObjectsClient.find.mockClear(); + }); + + it('searches visualization title and description', async () => { + const props = testProps(); + const { find } = props.savedObjectsClient; + await findListItems( + props.savedObjectsClient, + { get: mockGetTypes, getAliases: mockGetAliases }, + props.search, + props.size + ); + expect(find.mock.calls).toMatchObject([ + [ + { + type: ['visualization'], + searchFields: ['title^3', 'description'], + }, + ], + ]); + }); + + it('searches searchFields and types specified by app extensions', async () => { + const props = testProps(); + visTypes = [ + { + appExtensions: { + visualizations: { + docTypes: ['bazdoc', 'etc'], + searchFields: ['baz', 'bing'], + }, + }, + } as VisTypeAlias, + ]; + const { find } = props.savedObjectsClient; + await findListItems( + props.savedObjectsClient, + { get: mockGetTypes, getAliases: mockGetAliases }, + props.search, + props.size + ); + expect(find.mock.calls).toMatchObject([ + [ + { + type: ['bazdoc', 'etc', 'visualization'], + searchFields: ['baz', 'bing', 'title^3', 'description'], + }, + ], + ]); + }); + + it('deduplicates types and search fields', async () => { + const props = testProps(); + visTypes = [ + { + appExtensions: { + visualizations: { + docTypes: ['bazdoc', 'bar'], + searchFields: ['baz', 'bing', 'barfield'], + }, + }, + } as VisTypeAlias, + { + appExtensions: { + visualizations: { + docTypes: ['visualization', 'foo', 'bazdoc'], + searchFields: ['baz', 'bing', 'foofield'], + }, + }, + } as VisTypeAlias, + ]; + const { find } = props.savedObjectsClient; + await findListItems( + props.savedObjectsClient, + { get: mockGetTypes, getAliases: mockGetAliases }, + props.search, + props.size + ); + expect(find.mock.calls).toMatchObject([ + [ + { + type: ['bazdoc', 'bar', 'visualization', 'foo'], + searchFields: ['baz', 'bing', 'barfield', 'foofield', 'title^3', 'description'], + }, + ], + ]); + }); + + it('searches the search term prefix', async () => { + const props = { + ...testProps(), + search: 'ahoythere', + }; + const { find } = props.savedObjectsClient; + await findListItems( + props.savedObjectsClient, + { get: mockGetTypes, getAliases: mockGetAliases }, + props.search, + props.size + ); + expect(find.mock.calls).toMatchObject([ + [ + { + search: 'ahoythere*', + }, + ], + ]); + }); + + it('searches with references', async () => { + const props = { + ...testProps(), + references: [ + { type: 'foo', id: 'hello' }, + { type: 'bar', id: 'dolly' }, + ], + }; + const { find } = props.savedObjectsClient; + await findListItems( + props.savedObjectsClient, + { get: mockGetTypes, getAliases: mockGetAliases }, + props.search, + props.size, + props.references + ); + expect(find.mock.calls).toMatchObject([ + [ + { + hasReference: [ + { type: 'foo', id: 'hello' }, + { type: 'bar', id: 'dolly' }, + ], + }, + ], + ]); + }); + + it('uses type-specific toListItem function, if available', async () => { + const props = testProps(); + + visTypes = [ + { + appExtensions: { + visualizations: { + docTypes: ['wizard'], + toListItem(savedObject) { + return { + id: savedObject.id, + title: `${(savedObject.attributes as { label: string }).label} THE GRAY`, + }; + }, + }, + }, + } as VisTypeAlias, + ]; + (props.savedObjectsClient.find as jest.Mock).mockImplementationOnce(async () => ({ + total: 2, + savedObjects: [ + { + id: 'lotr', + type: 'wizard', + attributes: { label: 'Gandalf' }, + }, + { + id: 'wat', + type: 'visualization', + attributes: { title: 'WATEVER', typeName: 'test' }, + }, + ], + })); + + const items = await findListItems( + props.savedObjectsClient, + { get: mockGetTypes, getAliases: mockGetAliases }, + props.search, + props.size + ); + expect(items).toEqual({ + total: 2, + hits: [ + { + id: 'lotr', + references: undefined, + title: 'Gandalf THE GRAY', + }, + { + id: 'wat', + references: undefined, + icon: undefined, + savedObjectType: 'visualization', + editUrl: '/edit/wat', + type: 'test', + typeName: 'test', + typeTitle: undefined, + title: 'WATEVER', + url: '#/edit/wat', + }, + ], + }); + }); + }); +}); diff --git a/src/plugins/visualizations/public/utils/saved_visualize_utils.ts b/src/plugins/visualizations/public/utils/saved_visualize_utils.ts new file mode 100644 index 0000000000000..a28ee9486c4d2 --- /dev/null +++ b/src/plugins/visualizations/public/utils/saved_visualize_utils.ts @@ -0,0 +1,403 @@ +/* + * Copyright 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 _ from 'lodash'; +import type { + SavedObjectsFindOptionsReference, + SavedObjectsFindOptions, + SavedObjectsClientContract, + SavedObjectAttributes, + SavedObjectReference, +} from 'kibana/public'; +import type { OverlayStart } from '../../../../core/public'; +import { SavedObjectNotFound } from '../../../kibana_utils/public'; +import { + extractSearchSourceReferences, + injectSearchSourceReferences, + parseSearchSourceJSON, + DataPublicPluginStart, +} from '../../../../plugins/data/public'; +import { + checkForDuplicateTitle, + saveWithConfirmation, + isErrorNonFatal, +} from '../../../../plugins/saved_objects/public'; +import type { SavedObjectsTaggingApi } from '../../../saved_objects_tagging_oss/public'; +import type { SpacesPluginStart } from '../../../../../x-pack/plugins/spaces/public'; +import { VisualizationsAppExtension } from '../vis_types/vis_type_alias_registry'; +import type { + VisSavedObject, + SerializedVis, + ISavedVis, + SaveVisOptions, + GetVisOptions, +} from '../types'; +import type { TypesStart, BaseVisType } from '../vis_types'; +// @ts-ignore +import { updateOldState } from '../legacy/vis_update_state'; +import { injectReferences, extractReferences } from './saved_visualization_references'; + +export const SAVED_VIS_TYPE = 'visualization'; + +const getDefaults = (opts: GetVisOptions) => ({ + title: '', + visState: !opts.type ? null : { type: opts.type }, + uiStateJSON: '{}', + description: '', + savedSearchId: opts.savedSearchId, + version: 1, +}); + +export function getFullPath(id: string) { + return `/app/visualize#/edit/${id}`; +} + +export function urlFor(id: string) { + return `#/edit/${encodeURIComponent(id)}`; +} + +export function mapHitSource( + visTypes: Pick, + { + attributes, + id, + references, + }: { + attributes: SavedObjectAttributes; + id: string; + references: SavedObjectReference[]; + } +) { + const newAttributes: { + id: string; + references: SavedObjectReference[]; + url: string; + savedObjectType?: string; + editUrl?: string; + type?: BaseVisType; + icon?: BaseVisType['icon']; + image?: BaseVisType['image']; + typeTitle?: BaseVisType['title']; + error?: string; + } = { + id, + references, + url: urlFor(id), + ...attributes, + }; + + let typeName = attributes.typeName; + if (attributes.visState) { + try { + typeName = JSON.parse(String(attributes.visState)).type; + } catch (e) { + /* missing typename handled below */ + } + } + + if (!typeName || !visTypes.get(typeName as string)) { + newAttributes.error = 'Unknown visualization type'; + return newAttributes; + } + + newAttributes.type = visTypes.get(typeName as string); + newAttributes.savedObjectType = 'visualization'; + newAttributes.icon = newAttributes.type?.icon; + newAttributes.image = newAttributes.type?.image; + newAttributes.typeTitle = newAttributes.type?.title; + newAttributes.editUrl = `/edit/${id}`; + + return newAttributes; +} + +export const convertToSerializedVis = (savedVis: VisSavedObject): SerializedVis => { + const { id, title, description, visState, uiStateJSON, searchSourceFields } = savedVis; + + const aggs = searchSourceFields && searchSourceFields.index ? visState.aggs || [] : visState.aggs; + + return { + id, + title, + type: visState.type, + description, + params: visState.params, + uiState: JSON.parse(uiStateJSON || '{}'), + data: { + aggs, + searchSource: searchSourceFields!, + savedSearchId: savedVis.savedSearchId, + }, + }; +}; + +export const convertFromSerializedVis = (vis: SerializedVis): ISavedVis => { + return { + id: vis.id, + title: vis.title, + description: vis.description, + visState: { + title: vis.title, + type: vis.type, + aggs: vis.data.aggs, + params: vis.params, + }, + uiStateJSON: JSON.stringify(vis.uiState), + searchSourceFields: vis.data.searchSource, + savedSearchId: vis.data.savedSearchId, + }; +}; + +export async function findListItems( + savedObjectsClient: SavedObjectsClientContract, + visTypes: Pick, + search: string, + size: number, + references?: SavedObjectsFindOptionsReference[] +) { + const visAliases = visTypes.getAliases(); + const extensions = visAliases + .map((v) => v.appExtensions?.visualizations) + .filter(Boolean) as VisualizationsAppExtension[]; + const extensionByType = extensions.reduce((acc, m) => { + return m!.docTypes.reduce((_acc, type) => { + acc[type] = m; + return acc; + }, acc); + }, {} as { [visType: string]: VisualizationsAppExtension }); + const searchOption = (field: string, ...defaults: string[]) => + _(extensions).map(field).concat(defaults).compact().flatten().uniq().value() as string[]; + const searchOptions: SavedObjectsFindOptions = { + type: searchOption('docTypes', 'visualization'), + searchFields: searchOption('searchFields', 'title^3', 'description'), + search: search ? `${search}*` : undefined, + perPage: size, + page: 1, + defaultSearchOperator: 'AND' as 'AND', + hasReference: references, + }; + + const { total, savedObjects } = await savedObjectsClient.find( + searchOptions + ); + + return { + total, + hits: savedObjects.map((savedObject) => { + const config = extensionByType[savedObject.type]; + + if (config) { + return { + ...config.toListItem(savedObject), + references: savedObject.references, + }; + } else { + return mapHitSource(visTypes, savedObject); + } + }), + }; +} + +export async function getSavedVisualization( + services: { + savedObjectsClient: SavedObjectsClientContract; + search: DataPublicPluginStart['search']; + dataViews: DataPublicPluginStart['dataViews']; + spaces?: SpacesPluginStart; + savedObjectsTagging?: SavedObjectsTaggingApi; + }, + opts?: GetVisOptions | string +): Promise { + if (typeof opts !== 'object') { + opts = { id: opts } as GetVisOptions; + } + + const id = (opts.id as string) || ''; + const savedObject = { + id, + migrationVersion: opts.migrationVersion, + displayName: SAVED_VIS_TYPE, + getEsType: () => SAVED_VIS_TYPE, + getDisplayName: () => SAVED_VIS_TYPE, + searchSource: opts.searchSource ? services.search.searchSource.createEmpty() : undefined, + } as VisSavedObject; + const defaultsProps = getDefaults(opts); + + if (!id) { + Object.assign(savedObject, defaultsProps); + return savedObject; + } + + const { + saved_object: resp, + outcome, + alias_target_id: aliasTargetId, + } = await services.savedObjectsClient.resolve(SAVED_VIS_TYPE, id); + + if (!resp._version) { + throw new SavedObjectNotFound(SAVED_VIS_TYPE, id || ''); + } + + const attributes = _.cloneDeep(resp.attributes); + + if (attributes.visState && typeof attributes.visState === 'string') { + attributes.visState = JSON.parse(attributes.visState); + } + + // assign the defaults to the response + _.defaults(attributes, defaultsProps); + + Object.assign(savedObject, attributes); + savedObject.lastSavedTitle = savedObject.title; + + savedObject.sharingSavedObjectProps = { + aliasTargetId, + outcome, + errorJSON: + outcome === 'conflict' && services.spaces + ? JSON.stringify({ + targetType: SAVED_VIS_TYPE, + sourceId: id, + targetSpace: (await services.spaces.getActiveSpace()).id, + }) + : undefined, + }; + + const meta = (attributes.kibanaSavedObjectMeta || {}) as SavedObjectAttributes; + + if (meta.searchSourceJSON) { + try { + let searchSourceValues = parseSearchSourceJSON(meta.searchSourceJSON as string); + + if (opts.searchSource) { + searchSourceValues = injectSearchSourceReferences( + searchSourceValues as any, + resp.references + ); + savedObject.searchSource = await services.search.searchSource.create(searchSourceValues); + } else { + savedObject.searchSourceFields = searchSourceValues; + } + } catch (error: any) { + throw error; + } + } + + if (resp.references && resp.references.length > 0) { + injectReferences(savedObject, resp.references); + } + + if (services.savedObjectsTagging) { + savedObject.tags = services.savedObjectsTagging.ui.getTagIdsFromReferences(resp.references); + } + + savedObject.visState = await updateOldState(savedObject.visState); + if (savedObject.searchSourceFields?.index) { + await services.dataViews.get(savedObject.searchSourceFields.index as any); + } + + return savedObject; +} + +export async function saveVisualization( + savedObject: VisSavedObject, + { + confirmOverwrite = false, + isTitleDuplicateConfirmed = false, + onTitleDuplicate, + copyOnSave = false, + }: SaveVisOptions, + services: { + savedObjectsClient: SavedObjectsClientContract; + overlays: OverlayStart; + savedObjectsTagging?: SavedObjectsTaggingApi; + } +) { + // Save the original id in case the save fails. + const originalId = savedObject.id; + // Read https://github.com/elastic/kibana/issues/9056 and + // https://github.com/elastic/kibana/issues/9012 for some background into why this copyOnSave variable + // exists. + // The goal is to move towards a better rename flow, but since our users have been conditioned + // to expect a 'save as' flow during a rename, we are keeping the logic the same until a better + // UI/UX can be worked out. + if (copyOnSave) { + delete savedObject.id; + } + + const attributes: SavedObjectAttributes = { + visState: JSON.stringify(savedObject.visState), + title: savedObject.title, + uiStateJSON: savedObject.uiStateJSON, + description: savedObject.description, + savedSearchId: savedObject.savedSearchId, + version: savedObject.version, + }; + let references: SavedObjectReference[] = []; + + if (savedObject.searchSource) { + const { searchSourceJSON, references: searchSourceReferences } = + savedObject.searchSource.serialize(); + attributes.kibanaSavedObjectMeta = { searchSourceJSON }; + references.push(...searchSourceReferences); + } + + if (savedObject.searchSourceFields) { + const [searchSourceFields, searchSourceReferences] = extractSearchSourceReferences( + savedObject.searchSourceFields + ); + const searchSourceJSON = JSON.stringify(searchSourceFields); + attributes.kibanaSavedObjectMeta = { searchSourceJSON }; + references.push(...searchSourceReferences); + } + + if (services.savedObjectsTagging) { + references = services.savedObjectsTagging.ui.updateTagsReferences( + references, + savedObject.tags || [] + ); + } + + const extractedRefs = extractReferences({ attributes, references }); + + if (!extractedRefs.references) { + throw new Error('References not returned from extractReferences'); + } + + try { + await checkForDuplicateTitle( + { + ...savedObject, + copyOnSave, + } as any, + isTitleDuplicateConfirmed, + onTitleDuplicate, + services as any + ); + const createOpt = { + id: savedObject.id, + migrationVersion: savedObject.migrationVersion, + references: extractedRefs.references, + }; + const resp = confirmOverwrite + ? await saveWithConfirmation(attributes, savedObject, createOpt, services) + : await services.savedObjectsClient.create(SAVED_VIS_TYPE, extractedRefs.attributes, { + ...createOpt, + overwrite: true, + }); + + savedObject.id = resp.id; + savedObject.lastSavedTitle = savedObject.title; + return savedObject.id; + } catch (err: any) { + savedObject.id = originalId; + if (isErrorNonFatal(err)) { + return ''; + } + return Promise.reject(err); + } +} diff --git a/src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx b/src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx index 1d297a8d9ebcb..81709abb800b9 100644 --- a/src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx +++ b/src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx @@ -71,9 +71,9 @@ export class SearchSelection extends React.Component { type: 'index-pattern', getIconForSavedObject: () => 'indexPatternApp', name: i18n.translate( - 'visualizations.newVisWizard.searchSelection.savedObjectType.dataView', + 'visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern', { - defaultMessage: 'Data view', + defaultMessage: 'Index pattern', } ), }, diff --git a/src/plugins/visualizations/tsconfig.json b/src/plugins/visualizations/tsconfig.json index 65ab83d5e0bae..eeaed655c3e73 100644 --- a/src/plugins/visualizations/tsconfig.json +++ b/src/plugins/visualizations/tsconfig.json @@ -23,5 +23,6 @@ { "path": "../usage_collection/tsconfig.json" }, { "path": "../kibana_utils/tsconfig.json" }, { "path": "../discover/tsconfig.json" }, + { "path": "../../../x-pack/plugins/spaces/tsconfig.json" }, ] } diff --git a/src/plugins/visualize/kibana.json b/src/plugins/visualize/kibana.json index afa9e3ce055b2..bfb23bec2111c 100644 --- a/src/plugins/visualize/kibana.json +++ b/src/plugins/visualize/kibana.json @@ -17,7 +17,8 @@ "home", "share", "savedObjectsTaggingOss", - "usageCollection" + "usageCollection", + "spaces" ], "requiredBundles": [ "kibanaUtils", diff --git a/src/plugins/visualize/public/application/components/visualize_editor_common.test.tsx b/src/plugins/visualize/public/application/components/visualize_editor_common.test.tsx new file mode 100644 index 0000000000000..2c8478492005f --- /dev/null +++ b/src/plugins/visualize/public/application/components/visualize_editor_common.test.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { shallow, mount } from 'enzyme'; +import { VisualizeEditorCommon } from './visualize_editor_common'; +import { VisualizeEditorVisInstance } from '../types'; + +const mockGetLegacyUrlConflict = jest.fn(); +const mockRedirectLegacyUrl = jest.fn(() => Promise.resolve()); +jest.mock('../../../../kibana_react/public', () => ({ + useKibana: jest.fn(() => ({ + services: { + spaces: { + ui: { + redirectLegacyUrl: mockRedirectLegacyUrl, + components: { + getLegacyUrlConflict: mockGetLegacyUrlConflict, + }, + }, + }, + history: { + location: { + search: '?_g=test', + }, + }, + http: { + basePath: { + prepend: (url: string) => url, + }, + }, + }, + })), + withKibana: jest.fn((comp) => comp), +})); + +describe('VisualizeEditorCommon', () => { + it('should display a conflict callout if saved object conflicts', async () => { + shallow( + {}} + hasUnappliedChanges={false} + isEmbeddableRendered={false} + onAppLeave={() => {}} + visEditorRef={React.createRef()} + visInstance={ + { + savedVis: { + id: 'test', + sharingSavedObjectProps: { + outcome: 'conflict', + aliasTargetId: 'alias_id', + }, + }, + vis: { + type: { + title: 'TSVB', + }, + }, + } as VisualizeEditorVisInstance + } + /> + ); + expect(mockGetLegacyUrlConflict).toHaveBeenCalledWith({ + currentObjectId: 'test', + objectNoun: 'TSVB visualization', + otherObjectId: 'alias_id', + otherObjectPath: '#/edit/alias_id?_g=test', + }); + }); + + it('should redirect to new id if saved object aliasMatch', async () => { + mount( + {}} + hasUnappliedChanges={false} + isEmbeddableRendered={false} + onAppLeave={() => {}} + visEditorRef={React.createRef()} + visInstance={ + { + savedVis: { + id: 'test', + sharingSavedObjectProps: { + outcome: 'aliasMatch', + aliasTargetId: 'alias_id', + }, + }, + vis: { + type: { + title: 'TSVB', + }, + }, + } as VisualizeEditorVisInstance + } + /> + ); + expect(mockRedirectLegacyUrl).toHaveBeenCalledWith( + '#/edit/alias_id?_g=test', + 'TSVB visualization' + ); + }); +}); diff --git a/src/plugins/visualize/public/application/components/visualize_editor_common.tsx b/src/plugins/visualize/public/application/components/visualize_editor_common.tsx index a03073e61f59c..6268ba5c936ef 100644 --- a/src/plugins/visualize/public/application/components/visualize_editor_common.tsx +++ b/src/plugins/visualize/public/application/components/visualize_editor_common.tsx @@ -7,15 +7,19 @@ */ import './visualize_editor.scss'; -import React, { RefObject } from 'react'; +import React, { RefObject, useCallback, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { EuiScreenReaderOnly } from '@elastic/eui'; import { AppMountParameters } from 'kibana/public'; import { VisualizeTopNav } from './visualize_top_nav'; import { ExperimentalVisInfo } from './experimental_vis_info'; +import { useKibana } from '../../../../kibana_react/public'; +import { urlFor } from '../../../../visualizations/public'; import { SavedVisInstance, VisualizeAppState, + VisualizeServices, VisualizeAppStateContainer, VisualizeEditorVisInstance, } from '../types'; @@ -53,6 +57,55 @@ export const VisualizeEditorCommon = ({ embeddableId, visEditorRef, }: VisualizeEditorCommonProps) => { + const { services } = useKibana(); + + useEffect(() => { + async function aliasMatchRedirect() { + const sharingSavedObjectProps = visInstance?.savedVis.sharingSavedObjectProps; + if (services.spaces && sharingSavedObjectProps?.outcome === 'aliasMatch') { + // We found this object by a legacy URL alias from its old ID; redirect the user to the page with its new ID, preserving any URL hash + const newObjectId = sharingSavedObjectProps?.aliasTargetId; // This is always defined if outcome === 'aliasMatch' + const newPath = `${urlFor(newObjectId!)}${services.history.location.search}`; + await services.spaces.ui.redirectLegacyUrl( + newPath, + i18n.translate('visualize.legacyUrlConflict.objectNoun', { + defaultMessage: '{visName} visualization', + values: { + visName: visInstance?.vis?.type.title, + }, + }) + ); + return; + } + } + + aliasMatchRedirect(); + }, [visInstance?.savedVis.sharingSavedObjectProps, visInstance?.vis?.type.title, services]); + + const getLegacyUrlConflictCallout = useCallback(() => { + // This function returns a callout component *if* we have encountered a "legacy URL conflict" scenario + const currentObjectId = visInstance?.savedVis.id; + const sharingSavedObjectProps = visInstance?.savedVis.sharingSavedObjectProps; + if (services.spaces && sharingSavedObjectProps?.outcome === 'conflict' && currentObjectId) { + // We have resolved to one object, but another object has a legacy URL alias associated with this ID/page. We should display a + // callout with a warning for the user, and provide a way for them to navigate to the other object. + const otherObjectId = sharingSavedObjectProps?.aliasTargetId!; // This is always defined if outcome === 'conflict' + const otherObjectPath = `${urlFor(otherObjectId)}${services.history.location.search}`; + return services.spaces.ui.components.getLegacyUrlConflict({ + objectNoun: i18n.translate('visualize.legacyUrlConflict.objectNoun', { + defaultMessage: '{visName} visualization', + values: { + visName: visInstance?.vis?.type.title, + }, + }), + currentObjectId, + otherObjectId, + otherObjectPath, + }); + } + return null; + }, [visInstance?.savedVis, services, visInstance?.vis?.type.title]); + return (

{visInstance && appState && currentAppState && ( @@ -74,6 +127,7 @@ export const VisualizeEditorCommon = ({ )} {visInstance?.vis?.type?.stage === 'experimental' && } {visInstance?.vis?.type?.getInfoMessage?.(visInstance.vis)} + {getLegacyUrlConflictCallout()} {visInstance && (

diff --git a/src/plugins/visualize/public/application/components/visualize_listing.tsx b/src/plugins/visualize/public/application/components/visualize_listing.tsx index 07c3d18b54b0d..7319a9b5e52f8 100644 --- a/src/plugins/visualize/public/application/components/visualize_listing.tsx +++ b/src/plugins/visualize/public/application/components/visualize_listing.tsx @@ -31,7 +31,6 @@ export const VisualizeListing = () => { chrome, dashboard, history, - savedVisualizations, toastNotifications, visualizations, stateTransferService, @@ -113,16 +112,16 @@ export const VisualizeListing = () => { } const isLabsEnabled = uiSettings.get(VISUALIZE_ENABLE_LABS_SETTING); - return savedVisualizations - .findListItems(searchTerm, { size: listingLimit, references }) - .then(({ total, hits }: { total: number; hits: object[] }) => ({ + return visualizations + .findListItems(searchTerm, listingLimit, references) + .then(({ total, hits }: { total: number; hits: Array> }) => ({ total, hits: hits.filter( (result: any) => isLabsEnabled || result.type?.stage !== 'experimental' ), })); }, - [listingLimit, savedVisualizations, uiSettings, savedObjectsTagging] + [listingLimit, uiSettings, savedObjectsTagging, visualizations] ); const deleteItems = useCallback( diff --git a/src/plugins/visualize/public/application/types.ts b/src/plugins/visualize/public/application/types.ts index 7e9f69163f5a6..4debd9a4a7b7d 100644 --- a/src/plugins/visualize/public/application/types.ts +++ b/src/plugins/visualize/public/application/types.ts @@ -42,6 +42,7 @@ import type { SavedObjectsStart, SavedObject } from 'src/plugins/saved_objects/p import type { EmbeddableStart, EmbeddableStateTransfer } from 'src/plugins/embeddable/public'; import type { UrlForwardingStart } from 'src/plugins/url_forwarding/public'; import type { PresentationUtilPluginStart } from 'src/plugins/presentation_util/public'; +import type { SpacesPluginStart } from '../../../../../x-pack/plugins/spaces/public'; import type { DashboardStart } from '../../../dashboard/public'; import type { SavedObjectsTaggingApi } from '../../../saved_objects_tagging_oss/public'; import type { UsageCollectionStart } from '../../../usage_collection/public'; @@ -94,7 +95,6 @@ export interface VisualizeServices extends CoreStart { dashboardCapabilities: Record>; visualizations: VisualizationsStart; savedObjectsPublic: SavedObjectsStart; - savedVisualizations: VisualizationsStart['savedVisualizationsLoader']; setActiveUrl: (newUrl: string) => void; createVisEmbeddableFromObject: VisualizationsStart['__LEGACY']['createVisEmbeddableFromObject']; restorePreviousUrl: () => void; @@ -105,6 +105,7 @@ export interface VisualizeServices extends CoreStart { presentationUtil: PresentationUtilPluginStart; usageCollection?: UsageCollectionStart; getKibanaVersion: () => string; + spaces?: SpacesPluginStart; } export interface SavedVisInstance { diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx index 0dc37ca00a6aa..9d1c93f25645c 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx @@ -14,7 +14,11 @@ import { parse } from 'query-string'; import { Capabilities } from 'src/core/public'; import { TopNavMenuData } from 'src/plugins/navigation/public'; -import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeInput } from '../../../../visualizations/public'; +import { + VISUALIZE_EMBEDDABLE_TYPE, + VisualizeInput, + getFullPath, +} from '../../../../visualizations/public'; import { showSaveModal, SavedObjectSaveModalOrigin, @@ -87,6 +91,7 @@ export const getTopNavConfig = ( data, application, chrome, + overlays, history, share, setActiveUrl, @@ -99,6 +104,8 @@ export const getTopNavConfig = ( presentationUtil, usageCollection, getKibanaVersion, + savedObjects, + visualizations, }: VisualizeServices ) => { const { vis, embeddableHandler } = visInstance; @@ -117,8 +124,10 @@ export const getTopNavConfig = ( /** * Called when the user clicks "Save" button. */ - async function doSave(saveOptions: SavedObjectSaveOpts & { dashboardId?: string }) { - const newlyCreated = !Boolean(savedVis.id) || savedVis.copyOnSave; + async function doSave( + saveOptions: SavedObjectSaveOpts & { dashboardId?: string; copyOnSave?: boolean } + ) { + const newlyCreated = !Boolean(savedVis.id) || saveOptions.copyOnSave; // vis.title was not bound and it's needed to reflect title into visState stateContainer.transitions.setVis({ title: savedVis.title, @@ -129,7 +138,7 @@ export const getTopNavConfig = ( setHasUnsavedChanges(false); try { - const id = await savedVis.save(saveOptions); + const id = await visualizations.saveVisualization(savedVis, saveOptions); if (id) { toastNotifications.addSuccess({ @@ -142,6 +151,8 @@ export const getTopNavConfig = ( 'data-test-subj': 'saveVisualizationSuccess', }); + chrome.recentlyAccessed.add(getFullPath(id), savedVis.title, String(id)); + if ((originatingApp && saveOptions.returnToOrigin) || saveOptions.dashboardId) { if (!embeddableId) { const appPath = `${VisualizeConstants.EDIT_PATH}/${encodeURIComponent(id)}`; @@ -164,7 +175,7 @@ export const getTopNavConfig = ( state: { type: VISUALIZE_EMBEDDABLE_TYPE, input: { savedObjectId: id }, - embeddableId: savedVis.copyOnSave ? undefined : embeddableId, + embeddableId: saveOptions.copyOnSave ? undefined : embeddableId, searchSessionId: data.search.session.getSessionId(), }, path, @@ -392,11 +403,10 @@ export const getTopNavConfig = ( const currentTitle = savedVis.title; savedVis.title = newTitle; embeddableHandler.updateInput({ title: newTitle }); - savedVis.copyOnSave = newCopyOnSave; savedVis.description = newDescription; - if (savedObjectsTagging && savedObjectsTagging.ui.hasTagDecoration(savedVis)) { - savedVis.setTags(selectedTags); + if (savedObjectsTagging) { + savedVis.tags = selectedTags; } const saveOptions = { @@ -405,6 +415,7 @@ export const getTopNavConfig = ( onTitleDuplicate, returnToOrigin, dashboardId: !!dashboardId ? dashboardId : undefined, + copyOnSave: newCopyOnSave, }; // If we're adding to a dashboard and not saving to library, @@ -457,9 +468,7 @@ export const getTopNavConfig = ( let tagOptions: React.ReactNode | undefined; if (savedObjectsTagging) { - if (savedVis && savedObjectsTagging.ui.hasTagDecoration(savedVis)) { - selectedTags = savedVis.getTags(); - } + selectedTags = savedVis.tags || []; tagOptions = ( ({ })), })); +let savedVisMock: VisSavedObject; + describe('getVisualizationInstance', () => { const serializedVisMock = { type: 'area', }; - let savedVisMock: VisSavedObject; let visMock: Vis; let mockServices: jest.Mocked; let subj: BehaviorSubject; @@ -47,13 +48,16 @@ describe('getVisualizationInstance', () => { data: {}, } as Vis; savedVisMock = {} as VisSavedObject; + // @ts-expect-error mockServices.data.search.showError.mockImplementation(() => {}); // @ts-expect-error - mockServices.savedVisualizations.get.mockImplementation(() => savedVisMock); - // @ts-expect-error mockServices.visualizations.convertToSerializedVis.mockImplementation(() => serializedVisMock); // @ts-expect-error + mockServices.visualizations.getSavedVisualization.mockImplementation( + (opts: unknown) => savedVisMock + ); + // @ts-expect-error mockServices.visualizations.createVis.mockImplementation(() => visMock); // @ts-expect-error mockServices.createVisEmbeddableFromObject.mockImplementation(() => ({ @@ -71,7 +75,9 @@ describe('getVisualizationInstance', () => { opts ); - expect(mockServices.savedVisualizations.get).toHaveBeenCalledWith(opts); + expect((mockServices.visualizations.getSavedVisualization as jest.Mock).mock.calls[0][0]).toBe( + opts + ); expect(savedVisMock.searchSourceFields).toEqual({ index: opts.indexPattern, }); @@ -98,7 +104,9 @@ describe('getVisualizationInstance', () => { visMock.type.setup = jest.fn(() => newVisObj); const { vis } = await getVisualizationInstance(mockServices, 'saved_vis_id'); - expect(mockServices.savedVisualizations.get).toHaveBeenCalledWith('saved_vis_id'); + expect((mockServices.visualizations.getSavedVisualization as jest.Mock).mock.calls[0][0]).toBe( + 'saved_vis_id' + ); expect(savedVisMock.searchSourceFields).toBeUndefined(); expect(visMock.type.setup).toHaveBeenCalledWith(visMock); expect(vis).toBe(newVisObj); @@ -128,7 +136,6 @@ describe('getVisualizationInstanceInput', () => { const serializedVisMock = { type: 'pie', }; - let savedVisMock: VisSavedObject; let visMock: Vis; let mockServices: jest.Mocked; let subj: BehaviorSubject; @@ -142,10 +149,12 @@ describe('getVisualizationInstanceInput', () => { } as Vis; savedVisMock = {} as VisSavedObject; // @ts-expect-error - mockServices.savedVisualizations.get.mockImplementation(() => savedVisMock); - // @ts-expect-error mockServices.visualizations.createVis.mockImplementation(() => visMock); // @ts-expect-error + mockServices.visualizations.getSavedVisualization.mockImplementation( + (opts: unknown) => savedVisMock + ); + // @ts-expect-error mockServices.createVisEmbeddableFromObject.mockImplementation(() => ({ getOutput$: jest.fn(() => subj.asObservable()), })); @@ -183,7 +192,7 @@ describe('getVisualizationInstanceInput', () => { const { savedVis, savedSearch, vis, embeddableHandler } = await getVisualizationInstanceFromInput(mockServices, input); - expect(mockServices.savedVisualizations.get).toHaveBeenCalled(); + expect(mockServices.visualizations.getSavedVisualization).toHaveBeenCalled(); expect(mockServices.visualizations.createVis).toHaveBeenCalledWith( serializedVisMock.type, input.savedVis diff --git a/src/plugins/visualize/public/application/utils/get_visualization_instance.ts b/src/plugins/visualize/public/application/utils/get_visualization_instance.ts index 88797ce264e25..faf25ff28cec0 100644 --- a/src/plugins/visualize/public/application/utils/get_visualization_instance.ts +++ b/src/plugins/visualize/public/application/utils/get_visualization_instance.ts @@ -66,14 +66,15 @@ export const getVisualizationInstanceFromInput = async ( visualizeServices: VisualizeServices, input: VisualizeInput ) => { - const { visualizations, savedVisualizations } = visualizeServices; + const { visualizations } = visualizeServices; const visState = input.savedVis as SerializedVis; /** * A saved vis is needed even in by value mode to support 'save to library' which converts the 'by value' * state of the visualization, into a new saved object. */ - const savedVis: VisSavedObject = await savedVisualizations.get(); + const savedVis: VisSavedObject = await visualizations.getSavedVisualization(); + if (visState.uiState && Object.keys(visState.uiState).length !== 0) { savedVis.uiStateJSON = JSON.stringify(visState.uiState); } @@ -107,8 +108,8 @@ export const getVisualizationInstance = async ( */ opts?: Record | string ) => { - const { visualizations, savedVisualizations } = visualizeServices; - const savedVis: VisSavedObject = await savedVisualizations.get(opts); + const { visualizations } = visualizeServices; + const savedVis: VisSavedObject = await visualizations.getSavedVisualization(opts); if (typeof opts !== 'string') { savedVis.searchSourceFields = { index: opts?.indexPattern } as SearchSourceFields; diff --git a/src/plugins/visualize/public/application/utils/mocks.ts b/src/plugins/visualize/public/application/utils/mocks.ts index a7029071851ca..f26c81ed99a89 100644 --- a/src/plugins/visualize/public/application/utils/mocks.ts +++ b/src/plugins/visualize/public/application/utils/mocks.ts @@ -26,7 +26,6 @@ export const createVisualizeServicesMock = () => { location: { pathname: '' }, }, visualizations, - savedVisualizations: visualizations.savedVisualizationsLoader, createVisEmbeddableFromObject: visualizations.__LEGACY.createVisEmbeddableFromObject, } as unknown as jest.Mocked; }; diff --git a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.test.ts b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.test.ts index b142f3fcd4061..f81744326365a 100644 --- a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.test.ts +++ b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.test.ts @@ -22,7 +22,6 @@ import { createEmbeddableStateTransferMock } from '../../../../../embeddable/pub const mockDefaultEditorControllerDestroy = jest.fn(); const mockEmbeddableHandlerDestroy = jest.fn(); const mockEmbeddableHandlerRender = jest.fn(); -const mockSavedVisDestroy = jest.fn(); const savedVisId = '9ca7aa90-b892-11e8-a6d9-e546fe2bba5f'; const mockSavedVisInstance = { embeddableHandler: { @@ -32,7 +31,6 @@ const mockSavedVisInstance = { savedVis: { id: savedVisId, title: 'Test Vis', - destroy: mockSavedVisDestroy, }, vis: { type: {}, @@ -103,7 +101,6 @@ describe('useSavedVisInstance', () => { mockDefaultEditorControllerDestroy.mockClear(); mockEmbeddableHandlerDestroy.mockClear(); mockEmbeddableHandlerRender.mockClear(); - mockSavedVisDestroy.mockClear(); toastNotifications.addWarning.mockClear(); mockGetVisualizationInstance.mockClear(); }); @@ -153,7 +150,6 @@ describe('useSavedVisInstance', () => { expect(mockDefaultEditorControllerDestroy.mock.calls.length).toBe(1); expect(mockEmbeddableHandlerDestroy).not.toHaveBeenCalled(); - expect(mockSavedVisDestroy.mock.calls.length).toBe(1); }); }); @@ -236,7 +232,6 @@ describe('useSavedVisInstance', () => { unmount(); expect(mockDefaultEditorControllerDestroy).not.toHaveBeenCalled(); expect(mockEmbeddableHandlerDestroy.mock.calls.length).toBe(1); - expect(mockSavedVisDestroy.mock.calls.length).toBe(1); }); }); }); diff --git a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts index 965951bfbd88d..b5919ec074966 100644 --- a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts +++ b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts @@ -176,9 +176,6 @@ export const useSavedVisInstance = ( } else if (state.savedVisInstance?.embeddableHandler) { state.savedVisInstance.embeddableHandler.destroy(); } - if (state.savedVisInstance?.savedVis) { - state.savedVisInstance.savedVis.destroy(); - } }; }, [state]); diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts index b128c09209743..c9df6a6ec57d8 100644 --- a/src/plugins/visualize/public/plugin.ts +++ b/src/plugins/visualize/public/plugin.ts @@ -28,6 +28,7 @@ import { createKbnUrlStateStorage, withNotifyOnErrors, } from '../../kibana_utils/public'; +import type { SpacesPluginStart } from '../../../../x-pack/plugins/spaces/public'; import { VisualizeConstants } from './application/visualize_constants'; import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public'; @@ -61,6 +62,7 @@ export interface VisualizePluginStartDependencies { savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; presentationUtil: PresentationUtilPluginStart; usageCollection?: UsageCollectionStart; + spaces: SpacesPluginStart; } export interface VisualizePluginSetupDependencies { @@ -192,7 +194,6 @@ export class VisualizePlugin data: pluginsStart.data, localStorage: new Storage(localStorage), navigation: pluginsStart.navigation, - savedVisualizations: pluginsStart.visualizations.savedVisualizationsLoader, share: pluginsStart.share, toastNotifications: coreStart.notifications.toasts, visualizeCapabilities: coreStart.application.capabilities.visualize, @@ -212,6 +213,7 @@ export class VisualizePlugin presentationUtil: pluginsStart.presentationUtil, usageCollection: pluginsStart.usageCollection, getKibanaVersion: () => this.initializerContext.env.packageInfo.version, + spaces: pluginsStart.spaces, }; params.element.classList.add('visAppWrapper'); diff --git a/src/plugins/visualize/tsconfig.json b/src/plugins/visualize/tsconfig.json index 3f1f7487085bf..9c1e3fd72ff8b 100644 --- a/src/plugins/visualize/tsconfig.json +++ b/src/plugins/visualize/tsconfig.json @@ -24,6 +24,7 @@ { "path": "../kibana_react/tsconfig.json" }, { "path": "../home/tsconfig.json" }, { "path": "../presentation_util/tsconfig.json" }, - { "path": "../discover/tsconfig.json" } + { "path": "../discover/tsconfig.json" }, + { "path": "../../../x-pack/plugins/spaces/tsconfig.json" }, ] } diff --git a/test/api_integration/apis/search/bsearch.ts b/test/api_integration/apis/search/bsearch.ts index f80bc1d0d9dfa..6aee2b542da0f 100644 --- a/test/api_integration/apis/search/bsearch.ts +++ b/test/api_integration/apis/search/bsearch.ts @@ -29,28 +29,25 @@ export default function ({ getService }: FtrProviderContext) { describe('bsearch', () => { describe('post', () => { it('should return 200 a single response', async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set({ 'X-Chunk-Encoding': '' }) - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, + const resp = await supertest.post(`/internal/bsearch`).send({ + batch: [ + { + request: { + params: { + index: '.kibana', + body: { + query: { + match_all: {}, }, }, }, - options: { - strategy: 'es', - }, }, - ], - }); + options: { + strategy: 'es', + }, + }, + ], + }); const jsonBody = parseBfetchResponse(resp); @@ -62,28 +59,25 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 200 a single response from compressed', async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set({ 'X-Chunk-Encoding': 'deflate' }) - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, + const resp = await supertest.post(`/internal/bsearch?compress=true`).send({ + batch: [ + { + request: { + params: { + index: '.kibana', + body: { + query: { + match_all: {}, }, }, }, - options: { - strategy: 'es', - }, }, - ], - }); + options: { + strategy: 'es', + }, + }, + ], + }); const jsonBody = parseBfetchResponse(resp, true); diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index 2e965c275d6dd..72f45e1fedb4d 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -367,8 +367,7 @@ export default function ({ getService, getPageObjects }) { }); }); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/113745 - describe.skip('creating and using Painless date scripted fields', function describeIndexTests() { + describe('creating and using Painless date scripted fields', function describeIndexTests() { const scriptedPainlessFieldName2 = 'painDate'; it('should create scripted field', async function () { @@ -384,7 +383,7 @@ export default function ({ getService, getPageObjects }) { 'date', { format: 'date', datePattern: 'YYYY-MM-DD HH:00' }, '1', - "doc['utc_time'].value.getMillis() + (1000) * 60 * 60" + "doc['utc_time'].value.toEpochMilli() + (1000) * 60 * 60" ); await retry.try(async function () { expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be( diff --git a/test/interpreter_functional/screenshots/baseline/metric_all_data.png b/test/interpreter_functional/screenshots/baseline/metric_all_data.png index 66357a371a5be..18dca6c2c39c2 100644 Binary files a/test/interpreter_functional/screenshots/baseline/metric_all_data.png and b/test/interpreter_functional/screenshots/baseline/metric_all_data.png differ diff --git a/test/interpreter_functional/screenshots/baseline/metric_empty_data.png b/test/interpreter_functional/screenshots/baseline/metric_empty_data.png index 06cd781415ab0..db1dda5083de2 100644 Binary files a/test/interpreter_functional/screenshots/baseline/metric_empty_data.png and b/test/interpreter_functional/screenshots/baseline/metric_empty_data.png differ diff --git a/test/interpreter_functional/screenshots/baseline/metric_multi_metric_data.png b/test/interpreter_functional/screenshots/baseline/metric_multi_metric_data.png index 5888fba713bef..1e85944250156 100644 Binary files a/test/interpreter_functional/screenshots/baseline/metric_multi_metric_data.png and b/test/interpreter_functional/screenshots/baseline/metric_multi_metric_data.png differ diff --git a/test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png b/test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png index 8a5fd9d7a7285..bcf33d9171193 100644 Binary files a/test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png and b/test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png differ diff --git a/test/interpreter_functional/screenshots/baseline/metric_single_metric_data.png b/test/interpreter_functional/screenshots/baseline/metric_single_metric_data.png index 315653ee2b940..a82654240e374 100644 Binary files a/test/interpreter_functional/screenshots/baseline/metric_single_metric_data.png and b/test/interpreter_functional/screenshots/baseline/metric_single_metric_data.png differ diff --git a/test/interpreter_functional/snapshots/baseline/combined_test3.json b/test/interpreter_functional/snapshots/baseline/combined_test3.json index 64b1052552c8f..107d3fcbc5c54 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test3.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test3.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/final_output_test.json b/test/interpreter_functional/snapshots/baseline/final_output_test.json index 64b1052552c8f..107d3fcbc5c54 100644 --- a/test/interpreter_functional/snapshots/baseline/final_output_test.json +++ b/test/interpreter_functional/snapshots/baseline/final_output_test.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_all_data.json b/test/interpreter_functional/snapshots/baseline/metric_all_data.json index 0e1e5a723373f..9c10b53ce8604 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_all_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_empty_data.json b/test/interpreter_functional/snapshots/baseline/metric_empty_data.json index c318121535c8f..6fa08239f422d 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_empty_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_empty_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json index fc8622a818dec..4410447d2bb20 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json index 95c011f9259b9..2abb3070c3d05 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json +++ b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json index f4a8cd1f14e18..cce892a2f8c6f 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_2.json b/test/interpreter_functional/snapshots/baseline/partial_test_2.json index 64b1052552c8f..107d3fcbc5c54 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_2.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_2.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test3.json b/test/interpreter_functional/snapshots/baseline/step_output_test3.json index 64b1052552c8f..107d3fcbc5c54 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test3.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test3.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test3.json b/test/interpreter_functional/snapshots/session/combined_test3.json index 64b1052552c8f..107d3fcbc5c54 100644 --- a/test/interpreter_functional/snapshots/session/combined_test3.json +++ b/test/interpreter_functional/snapshots/session/combined_test3.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/final_output_test.json b/test/interpreter_functional/snapshots/session/final_output_test.json index 64b1052552c8f..107d3fcbc5c54 100644 --- a/test/interpreter_functional/snapshots/session/final_output_test.json +++ b/test/interpreter_functional/snapshots/session/final_output_test.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_all_data.json b/test/interpreter_functional/snapshots/session/metric_all_data.json index 0e1e5a723373f..9c10b53ce8604 100644 --- a/test/interpreter_functional/snapshots/session/metric_all_data.json +++ b/test/interpreter_functional/snapshots/session/metric_all_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_empty_data.json b/test/interpreter_functional/snapshots/session/metric_empty_data.json index c318121535c8f..6fa08239f422d 100644 --- a/test/interpreter_functional/snapshots/session/metric_empty_data.json +++ b/test/interpreter_functional/snapshots/session/metric_empty_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json index fc8622a818dec..4410447d2bb20 100644 --- a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json +++ b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json index 95c011f9259b9..2abb3070c3d05 100644 --- a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json +++ b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json index f4a8cd1f14e18..cce892a2f8c6f 100644 --- a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_2.json b/test/interpreter_functional/snapshots/session/partial_test_2.json index 64b1052552c8f..107d3fcbc5c54 100644 --- a/test/interpreter_functional/snapshots/session/partial_test_2.json +++ b/test/interpreter_functional/snapshots/session/partial_test_2.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test3.json b/test/interpreter_functional/snapshots/session/step_output_test3.json index 64b1052552c8f..107d3fcbc5c54 100644 --- a/test/interpreter_functional/snapshots/session/step_output_test3.json +++ b/test/interpreter_functional/snapshots/session/step_output_test3.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/utilities/templates/visual_regression_gallery.handlebars b/utilities/templates/visual_regression_gallery.handlebars deleted file mode 100644 index 60817dec5fcf2..0000000000000 --- a/utilities/templates/visual_regression_gallery.handlebars +++ /dev/null @@ -1,297 +0,0 @@ - - - - Kibana Visual Regression Gallery - - - - - - - - -
- Kibana Visual Regression Gallery -
- -
- {{branch}} - {{date}} -
- - {{#each comparisons as |comparison|}} -
- -
- ({{comparison.percentage}}%) {{comparison.name}} -
- -
-
Diff
- -
- Baseline -
-
- -
- -
- -
- - - - - -
- -
-
-
- {{/each}} - - - - - diff --git a/utilities/visual_regression.js b/utilities/visual_regression.js deleted file mode 100644 index fa4d48a2280d5..0000000000000 --- a/utilities/visual_regression.js +++ /dev/null @@ -1,141 +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 bluebird, { promisify } from 'bluebird'; -import Handlebars from 'handlebars'; -import fs from 'fs'; -import path from 'path'; -import { PNG } from 'pngjs'; -import pixelmatch from 'pixelmatch'; -import moment from 'moment'; -import SimpleGit from 'simple-git'; - -const readDirAsync = promisify(fs.readdir); -const readFileAsync = promisify(fs.readFile); -const writeFileAsync = promisify(fs.writeFile); - -Handlebars.registerHelper('lte', function lessThanEquals(value, threshold, options) { - if (value <= threshold) { - return options.fn(this); - } - return options.inverse(this); -}); - -Handlebars.registerHelper('gte', function greaterThanEquals(value, threshold, options) { - if (value >= threshold) { - return options.fn(this); - } - return options.inverse(this); -}); - -async function buildGallery(comparisons) { - const simpleGit = new SimpleGit(); - const asyncBranch = promisify(simpleGit.branch, simpleGit); - const branch = await asyncBranch(); - - const template = Handlebars.compile( - await readFileAsync( - path.resolve('./utilities/templates/visual_regression_gallery.handlebars'), - 'utf8' - ), - { knownHelpersOnly: true } - ); - - const html = template({ - date: moment().format('MMMM Do YYYY, h:mm:ss a'), - branch: branch.current, - hiddenThreshold: 0, - warningThreshold: 0.03, - comparisons, - }); - - return writeFileAsync( - path.resolve('./test/functional/screenshots/visual_regression_gallery.html'), - html - ); -} - -async function compareScreenshots() { - const SCREENSHOTS_DIR = 'test/functional/screenshots'; - const BASELINE_SCREENSHOTS_DIR = path.resolve(SCREENSHOTS_DIR, 'baseline'); - const DIFF_SCREENSHOTS_DIR = path.resolve(SCREENSHOTS_DIR, 'diff'); - const SESSION_SCREENSHOTS_DIR = path.resolve(SCREENSHOTS_DIR, 'session'); - - // We don't need to create the baseline dir because it's committed. - fs.mkdirSync(DIFF_SCREENSHOTS_DIR, { recursive: true }); - fs.mkdirSync(SESSION_SCREENSHOTS_DIR, { recursive: true }); - const files = await readDirAsync(SESSION_SCREENSHOTS_DIR); - const screenshots = files.filter((file) => file.indexOf('.png') !== -1); - - // We'll use this data to build a screenshot gallery in HTML. - return await bluebird.map(screenshots, async (screenshot) => { - // We're going to load image data and cache it in this object. - const comparison = { - name: screenshot, - change: undefined, - percentage: undefined, - imageData: { - session: undefined, - baseline: undefined, - diff: undefined, - }, - }; - - const sessionImagePath = path.resolve(SESSION_SCREENSHOTS_DIR, screenshot); - - const baselineImagePath = path.resolve(BASELINE_SCREENSHOTS_DIR, screenshot); - - const diffImagePath = path.resolve(DIFF_SCREENSHOTS_DIR, screenshot); - - const sessionImage = PNG.sync.read(await readFileAsync(sessionImagePath)); - const baselineImage = PNG.sync.read(await readFileAsync(baselineImagePath)); - const { width, height } = sessionImage; - const diff = new PNG({ width, height }); - - const numDiffPixels = pixelmatch( - sessionImage.data, - baselineImage.data, - diff.data, - width, - height, - { threshold: 0 } - ); - - await writeFileAsync(diffImagePath, PNG.sync.write(diff)); - - const change = numDiffPixels / (width * height); - const changePercentage = (change * 100).toFixed(2); - console.log(`(${changePercentage}%) ${screenshot}`); - comparison.percentage = changePercentage; - comparison.change = change; - - // Once the images have been diffed, we can load and store the image data. - comparison.imageData.session = await readFileAsync(sessionImagePath, 'base64'); - - comparison.imageData.baseline = await readFileAsync(baselineImagePath, 'base64'); - - comparison.imageData.diff = await readFileAsync(diffImagePath, 'base64'); - - return comparison; - }); -} - -export function run(done) { - compareScreenshots().then( - (screenshotComparisons) => { - // Once all of the data has been loaded, we can build the gallery. - buildGallery(screenshotComparisons).then(() => { - done(); - }); - }, - (error) => { - console.error(error); - done(false); - } - ); -} diff --git a/vars/tasks.groovy b/vars/tasks.groovy index 5c8f133331e55..1842e278282b1 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -146,14 +146,13 @@ def functionalXpack(Map params = [:]) { } } - //temporarily disable apm e2e test since it's breaking due to a version upgrade. - // whenChanged([ - // 'x-pack/plugins/apm/', - // ]) { - // if (githubPr.isPr()) { - // task(kibanaPipeline.functionalTestProcess('xpack-APMCypress', './test/scripts/jenkins_apm_cypress.sh')) - // } - // } + whenChanged([ + 'x-pack/plugins/apm/', + ]) { + if (githubPr.isPr()) { + task(kibanaPipeline.functionalTestProcess('xpack-APMCypress', './test/scripts/jenkins_apm_cypress.sh')) + } + } whenChanged([ 'x-pack/plugins/uptime/', diff --git a/x-pack/plugins/apm/dev_docs/apm_queries.md b/x-pack/plugins/apm/dev_docs/apm_queries.md index 7ca28f2e273ca..8508e5a173c85 100644 --- a/x-pack/plugins/apm/dev_docs/apm_queries.md +++ b/x-pack/plugins/apm/dev_docs/apm_queries.md @@ -1,3 +1,9 @@ +# Data model +Elastic APM agents capture different types of information from within their instrumented applications. These are known as events, and can be spans, transactions, errors, or metrics. You can find more information [here](https://www.elastic.co/guide/en/apm/get-started/current/apm-data-model.html). + +# Running examples +You can run the example queries on the [edge cluster](https://edge-oblt.elastic.dev/) or any another cluster that contains APM data. + # Transactions Transactions are stored in two different formats: @@ -34,6 +40,8 @@ A pre-aggregated document where `_doc_count` is the number of transaction events } ``` +You can find all the APM transaction fields [here](https://www.elastic.co/guide/en/apm/server/current/exported-fields-apm-transaction.html). + The decision to use aggregated transactions or not is determined in [`getSearchAggregatedTransactions`](https://github.com/elastic/kibana/blob/a2ac439f56313b7a3fc4708f54a4deebf2615136/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts#L53-L79) and then used to specify [the transaction index](https://github.com/elastic/kibana/blob/a2ac439f56313b7a3fc4708f54a4deebf2615136/x-pack/plugins/apm/server/lib/suggestions/get_suggestions.ts#L30-L32) and [the latency field](https://github.com/elastic/kibana/blob/a2ac439f56313b7a3fc4708f54a4deebf2615136/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts#L62-L65) ### Latency @@ -45,6 +53,7 @@ Noteworthy fields: `transaction.duration.us`, `transaction.duration.histogram` #### Transaction-based latency ```json +GET apm-*-transaction-*,traces-apm*/_search?terminate_after=1000 { "size": 0, "query": { @@ -61,6 +70,7 @@ Noteworthy fields: `transaction.duration.us`, `transaction.duration.histogram` #### Metric-based latency ```json +GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 { "size": 0, "query": { @@ -85,14 +95,64 @@ Throughput is the number of transactions per minute. This can be calculated usin Noteworthy fields: None (based on `doc_count`) -```js +#### Transaction-based throughput + +```json +GET apm-*-transaction-*,traces-apm*/_search?terminate_after=1000 +{ + "size": 0, + "query": { + "bool": { + "filter": [{ "terms": { "processor.event": ["transaction"] } }] + } + }, + "aggs": { + "timeseries": { + "date_histogram": { + "field": "@timestamp", + "fixed_interval": "60s" + }, + "aggs": { + "throughput": { + "rate": { + "unit": "minute" + } + } + } + } + } +} +``` + + +#### Metric-based throughput + +```json +GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 { "size": 0, "query": { - // same filters as for latency + "bool": { + "filter": [ + { "terms": { "processor.event": ["metric"] } }, + { "term": { "metricset.name": "transaction" } } + ] + } }, "aggs": { - "throughput": { "rate": { "unit": "minute" } } + "timeseries": { + "date_histogram": { + "field": "@timestamp", + "fixed_interval": "60s" + }, + "aggs": { + "throughput": { + "rate": { + "unit": "minute" + } + } + } + } } } ``` @@ -102,11 +162,41 @@ Noteworthy fields: None (based on `doc_count`) Failed transaction rate is the number of transactions with `event.outcome=failure` per minute. Noteworthy fields: `event.outcome` -```js +#### Transaction-based failed transaction rate + + ```json +GET apm-*-transaction-*,traces-apm*/_search?terminate_after=1000 { "size": 0, "query": { - // same filters as for latency + "bool": { + "filter": [{ "terms": { "processor.event": ["transaction"] } }] + } + }, + "aggs": { + "outcomes": { + "terms": { + "field": "event.outcome", + "include": ["failure", "success"] + } + } + } +} +``` + +#### Metric-based failed transaction rate + +```json +GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 +{ + "size": 0, + "query": { + "bool": { + "filter": [ + { "terms": { "processor.event": ["metric"] } }, + { "term": { "metricset.name": "transaction" } } + ] + } }, "aggs": { "outcomes": { @@ -121,7 +211,7 @@ Noteworthy fields: `event.outcome` # System metrics -System metrics are captured periodically (every 60 seconds by default). +System metrics are captured periodically (every 60 seconds by default). You can find all the System Metrics fields [here](https://www.elastic.co/guide/en/apm/server/current/exported-fields-system.html). ### CPU @@ -146,6 +236,7 @@ Noteworthy fields: `system.cpu.total.norm.pct`, `system.process.cpu.total.norm.p #### Query ```json +GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 { "size": 0, "query": { @@ -185,18 +276,17 @@ Noteworthy fields: `system.memory.actual.free`, `system.memory.total`, #### Query -```js +```json +GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 { "size": 0, "query": { "bool": { "filter": [ { "terms": { "processor.event": ["metric"] }}, - { "terms": { "metricset.name": ["app"] }} - - // ensure the memory fields exists + { "terms": { "metricset.name": ["app"] }}, { "exists": { "field": "system.memory.actual.free" }}, - { "exists": { "field": "system.memory.total" }}, + { "exists": { "field": "system.memory.total" }} ] } }, @@ -213,7 +303,7 @@ Noteworthy fields: `system.memory.actual.free`, `system.memory.total`, } ``` -Above example is overly simplified. In reality [we do a bit more](https://github.com/elastic/kibana/blob/fe9b5332e157fd456f81aecfd4ffa78d9e511a66/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts#L51-L71) to properly calculate memory usage inside containers +The above example is overly simplified. In reality [we do a bit more](https://github.com/elastic/kibana/blob/fe9b5332e157fd456f81aecfd4ffa78d9e511a66/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts#L51-L71) to properly calculate memory usage inside containers. Please note that an [Exists Query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-query.html) is used in the filter context in the query to ensure that the memory fields exist. @@ -268,6 +358,7 @@ Noteworthy fields: `transaction.name`, `transaction.type`, `span.type`, `span.su #### Query ```json +GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 { "size": 0, "query": { @@ -330,6 +421,7 @@ A pre-aggregated document with 73 span requests from opbeans-ruby to elasticsear The latency between a service and an (external) endpoint ```json +GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 { "size": 0, "query": { @@ -360,6 +452,7 @@ Captures the number of requests made from a service to an (external) endpoint #### Query ```json +GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 { "size": 0, "query": { @@ -372,10 +465,17 @@ Captures the number of requests made from a service to an (external) endpoint } }, "aggs": { - "throughput": { - "rate": { - "field": "span.destination.service.response_time.count", - "unit": "minute" + "timeseries": { + "date_histogram": { + "field": "@timestamp", + "fixed_interval": "60s" + }, + "aggs": { + "throughput": { + "rate": { + "unit": "minute" + } + } } } } @@ -390,27 +490,17 @@ Most Elasticsearch queries will need to have one or more filters. There are a co - stability: Running an aggregation on unrelated documents could cause the entire query to fail - performance: limiting the number of documents will make the query faster -```js +```json +GET apm-*-metric-*,metrics-apm*/_search?terminate_after=1000 { "query": { "bool": { "filter": [ - // service name { "term": { "service.name": "opbeans-go" }}, - - // service environment - { "term": { "service.environment": "testing" }} - - // transaction type - { "term": { "transaction.type": "request" }} - - // event type (possible values : transaction, span, metric, error) + { "term": { "service.environment": "testing" }}, + { "term": { "transaction.type": "request" }}, { "terms": { "processor.event": ["metric"] }}, - - // metric set is a subtype of `processor.event: metric` { "terms": { "metricset.name": ["transaction"] }}, - - // time range { "range": { "@timestamp": { @@ -422,5 +512,10 @@ Most Elasticsearch queries will need to have one or more filters. There are a co } ] } - }, + } +} ``` + +Possible values for `processor.event` are: `transaction`, `span`, `metric`, `error`. + +`metricset` is a subtype of `processor.event: metric`. Possible values are: `transaction`, `span_breakdown`, `transaction_breakdown`, `app`, `service_destination`, `agent_config` diff --git a/x-pack/plugins/apm/dev_docs/local_setup.md b/x-pack/plugins/apm/dev_docs/local_setup.md index fd350d81a35af..eaa99560400e6 100644 --- a/x-pack/plugins/apm/dev_docs/local_setup.md +++ b/x-pack/plugins/apm/dev_docs/local_setup.md @@ -46,4 +46,4 @@ This will create: All APM api endpoints accept `_inspect=true` as a query param that will output all Elasticsearch queries performed in that request. It will be available in the browser response and on localhost it is also available in the Kibana Node.js process output. Example: -`/api/apm/services/my_service?_inspect=true` +`/internal/apm/services/my_service?_inspect=true` diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/no_data_screen.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/no_data_screen.ts index 2d5c2a6f16228..1e954d9982295 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/no_data_screen.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/no_data_screen.ts @@ -6,7 +6,7 @@ */ /* eslint-disable @typescript-eslint/naming-convention */ -const apmIndicesSaveURL = '/api/apm/settings/apm-indices/save'; +const apmIndicesSaveURL = '/internal/apm/settings/apm-indices/save'; describe('No data screen', () => { describe('bypass no data screen on settings pages', () => { diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts index 679e2934f9c37..8457282f5c256 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/home.spec.ts @@ -17,11 +17,11 @@ const serviceInventoryHref = url.format({ const apisToIntercept = [ { - endpoint: '/api/apm/service?*', + endpoint: '/internal/apm/service?*', name: 'servicesMainStatistics', }, { - endpoint: '/api/apm/services/detailed_statistics?*', + endpoint: '/internal/apm/services/detailed_statistics?*', name: 'servicesDetailedStatistics', }, ]; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/header_filters.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/header_filters.spec.ts index 720e9b4b67e0a..6950a0bbadb9c 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/header_filters.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/header_filters.spec.ts @@ -16,43 +16,47 @@ const serviceOverviewHref = url.format({ const apisToIntercept = [ { - endpoint: '/api/apm/services/opbeans-node/transactions/charts/latency?*', + endpoint: + '/internal/apm/services/opbeans-node/transactions/charts/latency?*', name: 'latencyChartRequest', }, { - endpoint: '/api/apm/services/opbeans-node/throughput?*', + endpoint: '/internal/apm/services/opbeans-node/throughput?*', name: 'throughputChartRequest', }, { - endpoint: '/api/apm/services/opbeans-node/transactions/charts/error_rate?*', + endpoint: + '/internal/apm/services/opbeans-node/transactions/charts/error_rate?*', name: 'errorRateChartRequest', }, { endpoint: - '/api/apm/services/opbeans-node/transactions/groups/detailed_statistics?*', + '/internal/apm/services/opbeans-node/transactions/groups/detailed_statistics?*', name: 'transactionGroupsDetailedRequest', }, { endpoint: - '/api/apm/services/opbeans-node/service_overview_instances/detailed_statistics?*', + '/internal/apm/services/opbeans-node/service_overview_instances/detailed_statistics?*', name: 'instancesDetailedRequest', }, { endpoint: - '/api/apm/services/opbeans-node/service_overview_instances/main_statistics?*', + '/internal/apm/services/opbeans-node/service_overview_instances/main_statistics?*', name: 'instancesMainStatisticsRequest', }, { - endpoint: '/api/apm/services/opbeans-node/error_groups/main_statistics?*', + endpoint: + '/internal/apm/services/opbeans-node/error_groups/main_statistics?*', name: 'errorGroupsMainStatisticsRequest', }, { - endpoint: '/api/apm/services/opbeans-node/transaction/charts/breakdown?*', + endpoint: + '/internal/apm/services/opbeans-node/transaction/charts/breakdown?*', name: 'transactonBreakdownRequest', }, { endpoint: - '/api/apm/services/opbeans-node/transactions/groups/main_statistics?*', + '/internal/apm/services/opbeans-node/transactions/groups/main_statistics?*', name: 'transactionsGroupsMainStatisticsRequest', }, ]; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/instances_table.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/instances_table.spec.ts index 7bfa4689db4fe..c7d9fa4e32106 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/instances_table.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/instances_table.spec.ts @@ -18,17 +18,17 @@ const serviceOverviewHref = url.format({ const apisToIntercept = [ { endpoint: - '/api/apm/services/opbeans-java/service_overview_instances/main_statistics?*', + '/internal/apm/services/opbeans-java/service_overview_instances/main_statistics?*', name: 'instancesMainRequest', }, { endpoint: - '/api/apm/services/opbeans-java/service_overview_instances/detailed_statistics?*', + '/internal/apm/services/opbeans-java/service_overview_instances/detailed_statistics?*', name: 'instancesDetailsRequest', }, { endpoint: - '/api/apm/services/opbeans-java/service_overview_instances/details/31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad?*', + '/internal/apm/services/opbeans-java/service_overview_instances/details/31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad?*', name: 'instanceDetailsRequest', }, ]; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts index a0dc7082ca331..b0cf424b8067e 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/service_overview.spec.ts @@ -59,7 +59,7 @@ describe('Service Overview', () => { }); it('hides dependency tab when RUM service', () => { - cy.intercept('GET', '/api/apm/services/opbeans-rum/agent?*').as( + cy.intercept('GET', '/internal/apm/services/opbeans-rum/agent?*').as( 'agentRequest' ); cy.visit( diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/time_comparison.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/time_comparison.spec.ts index 2e05a2c062aaf..65a82a8ab6bdf 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/time_comparison.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_overview/time_comparison.spec.ts @@ -18,30 +18,32 @@ const serviceOverviewHref = url.format({ const apisToIntercept = [ { - endpoint: '/api/apm/services/opbeans-java/transactions/charts/latency?*', + endpoint: + '/internal/apm/services/opbeans-java/transactions/charts/latency?*', name: 'latencyChartRequest', }, { - endpoint: '/api/apm/services/opbeans-java/throughput?*', + endpoint: '/internal/apm/services/opbeans-java/throughput?*', name: 'throughputChartRequest', }, { - endpoint: '/api/apm/services/opbeans-java/transactions/charts/error_rate?*', + endpoint: + '/internal/apm/services/opbeans-java/transactions/charts/error_rate?*', name: 'errorRateChartRequest', }, { endpoint: - '/api/apm/services/opbeans-java/transactions/groups/detailed_statistics?*', + '/internal/apm/services/opbeans-java/transactions/groups/detailed_statistics?*', name: 'transactionGroupsDetailedRequest', }, { endpoint: - '/api/apm/services/opbeans-java/error_groups/detailed_statistics?*', + '/internal/apm/services/opbeans-java/error_groups/detailed_statistics?*', name: 'errorGroupsDetailedRequest', }, { endpoint: - '/api/apm/services/opbeans-java/service_overview_instances/detailed_statistics?*', + '/internal/apm/services/opbeans-java/service_overview_instances/detailed_statistics?*', name: 'instancesDetailedRequest', }, ]; 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 cb7b367fb390b..eac128a7e88c4 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 @@ -61,7 +61,8 @@ export function ErrorCountAlertTrigger(props: Props) { }); if (interval && start && end) { return callApmApi({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', + endpoint: + 'GET /internal/apm/alerts/chart_preview/transaction_error_count', params: { query: { environment: params.environment, 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 5327eb561bfc6..8957dfc823e44 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 @@ -99,7 +99,8 @@ export function TransactionDurationAlertTrigger(props: Props) { }); if (interval && start && end) { return callApmApi({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', + endpoint: + 'GET /internal/apm/alerts/chart_preview/transaction_duration', params: { query: { aggregationType: params.aggregationType, 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 3bad7ae15f658..ddddc4bbecbad 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 @@ -68,7 +68,8 @@ export function TransactionErrorRateAlertTrigger(props: Props) { }); if (interval && start && end) { return callApmApi({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', + endpoint: + 'GET /internal/apm/alerts/chart_preview/transaction_error_rate', params: { query: { environment: params.environment, 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 2d74187f9d83b..6685dddd87d7f 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 @@ -76,7 +76,7 @@ async function saveApmIndices({ apmIndices: Record; }) { await callApmApi({ - endpoint: 'POST /api/apm/settings/apm-indices/save', + endpoint: 'POST /internal/apm/settings/apm-indices/save', signal: null, params: { body: apmIndices, @@ -86,7 +86,8 @@ async function saveApmIndices({ clearCache(); } -type ApiResponse = APIReturnType<`GET /api/apm/settings/apm-index-settings`>; +type ApiResponse = + APIReturnType<`GET /internal/apm/settings/apm-index-settings`>; // avoid infinite loop by initializing the state outside the component const INITIAL_STATE: ApiResponse = { apmIndexSettings: [] }; @@ -103,7 +104,7 @@ export function ApmIndices() { (_callApmApi) => { if (canSave) { return _callApmApi({ - endpoint: `GET /api/apm/settings/apm-index-settings`, + endpoint: `GET /internal/apm/settings/apm-index-settings`, }); } }, 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 a60e685eacbde..b130c727cfcfe 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 @@ -35,7 +35,7 @@ interface Props { } type ApiResponse = - APIReturnType<'GET /api/apm/settings/anomaly-detection/environments'>; + APIReturnType<'GET /internal/apm/settings/anomaly-detection/environments'>; const INITIAL_DATA: ApiResponse = { environments: [] }; export function AddEnvironments({ @@ -50,7 +50,7 @@ export function AddEnvironments({ const { data = INITIAL_DATA, status } = useFetcher( (callApmApi) => callApmApi({ - endpoint: `GET /api/apm/settings/anomaly-detection/environments`, + endpoint: `GET /internal/apm/settings/anomaly-detection/environments`, }), [], { preservePreviousData: false } diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts index a5ab0a3002bb6..3e3493d60f839 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts @@ -28,7 +28,7 @@ export async function createJobs({ }) { try { await callApmApi({ - endpoint: 'POST /api/apm/settings/anomaly-detection/jobs', + endpoint: 'POST /internal/apm/settings/anomaly-detection/jobs', signal: null, params: { body: { environments }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx index 0fe7f9360de0c..8e1064a71647f 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx @@ -17,7 +17,7 @@ import { useLicenseContext } from '../../../../context/license/use_license_conte import { APIReturnType } from '../../../../services/rest/createCallApmApi'; export type AnomalyDetectionApiResponse = - APIReturnType<'GET /api/apm/settings/anomaly-detection/jobs'>; + APIReturnType<'GET /internal/apm/settings/anomaly-detection/jobs'>; const DEFAULT_VALUE: AnomalyDetectionApiResponse = { jobs: [], @@ -40,7 +40,7 @@ export function AnomalyDetection() { (callApmApi) => { if (canGetJobs) { return callApmApi({ - endpoint: `GET /api/apm/settings/anomaly-detection/jobs`, + endpoint: `GET /internal/apm/settings/anomaly-detection/jobs`, }); } }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/DeleteButton.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/DeleteButton.tsx index c6547aaff0671..f425979df71e6 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/DeleteButton.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/DeleteButton.tsx @@ -49,7 +49,7 @@ async function deleteConfig( ) { try { await callApmApi({ - endpoint: 'DELETE /api/apm/settings/custom_links/{id}', + endpoint: 'DELETE /internal/apm/settings/custom_links/{id}', signal: null, params: { path: { id: customLinkId }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/link_preview.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/link_preview.tsx index 726d4ba0d65ee..7ffeafedab4b3 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/link_preview.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/link_preview.tsx @@ -34,7 +34,7 @@ const fetchTransaction = debounce( async (filters: Filter[], callback: (transaction: Transaction) => void) => { const transaction = await callApmApi({ signal: null, - endpoint: 'GET /api/apm/settings/custom_links/transaction', + endpoint: 'GET /internal/apm/settings/custom_links/transaction', params: { query: convertFiltersToQuery(filters) }, }); callback(transaction); diff --git a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/saveCustomLink.ts b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/saveCustomLink.ts index aa20cf74d1512..6dcabc4adeda3 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/saveCustomLink.ts +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/create_edit_custom_link_flyout/saveCustomLink.ts @@ -35,7 +35,7 @@ export async function saveCustomLink({ if (id) { await callApmApi({ - endpoint: 'PUT /api/apm/settings/custom_links/{id}', + endpoint: 'PUT /internal/apm/settings/custom_links/{id}', signal: null, params: { path: { id }, @@ -44,7 +44,7 @@ export async function saveCustomLink({ }); } else { await callApmApi({ - endpoint: 'POST /api/apm/settings/custom_links', + endpoint: 'POST /internal/apm/settings/custom_links', signal: null, params: { body: customLink, diff --git a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/index.tsx index beea1d8276846..295cc4d008f80 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/customize_ui/custom_link/index.tsx @@ -38,7 +38,7 @@ export function CustomLinkOverview() { async (callApmApi) => { if (hasValidLicense) { return callApmApi({ - endpoint: 'GET /api/apm/settings/custom_links', + endpoint: 'GET /internal/apm/settings/custom_links', }); } }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx index 6b7538e61c130..ac32e22fa3ded 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx @@ -20,7 +20,7 @@ import { import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; type FleetMigrationCheckResponse = - APIReturnType<'GET /api/apm/fleet/migration_check'>; + APIReturnType<'GET /internal/apm/fleet/migration_check'>; const APM_DATA_STREAMS_MIGRATION_STATUS_LS = { value: '', @@ -46,7 +46,8 @@ export function Schema() { data = {} as FleetMigrationCheckResponse, status, } = useFetcher( - (callApi) => callApi({ endpoint: 'GET /api/apm/fleet/migration_check' }), + (callApi) => + callApi({ endpoint: 'GET /internal/apm/fleet/migration_check' }), [], { preservePreviousData: false } ); @@ -118,7 +119,7 @@ async function getUnsupportedApmServerConfigs( ) { try { const { unsupported } = await callApmApi({ - endpoint: 'GET /api/apm/fleet/apm_server_schema/unsupported', + endpoint: 'GET /internal/apm/fleet/apm_server_schema/unsupported', signal: null, }); return unsupported; @@ -142,7 +143,7 @@ async function createCloudApmPackagePolicy( updateLocalStorage(FETCH_STATUS.LOADING); try { const { cloudApmPackagePolicy } = await callApmApi({ - endpoint: 'POST /api/apm/fleet/cloud_apm_package_policy', + endpoint: 'POST /internal/apm/fleet/cloud_apm_package_policy', signal: null, }); updateLocalStorage(FETCH_STATUS.SUCCESS); diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx index 2733ee0ddbdba..1da022f3d933d 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx @@ -29,7 +29,7 @@ export function TraceLink() { (callApmApi) => { if (traceId) { return callApmApi({ - endpoint: 'GET /api/apm/traces/{traceId}/root_transaction', + endpoint: 'GET /internal/apm/traces/{traceId}/root_transaction', params: { path: { traceId, diff --git a/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_detail_dependencies_table.tsx b/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_detail_dependencies_table.tsx index f98358e3a9c27..be493f8a98b1c 100644 --- a/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_detail_dependencies_table.tsx +++ b/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_detail_dependencies_table.tsx @@ -44,7 +44,7 @@ export function BackendDetailDependenciesTable() { } return callApmApi({ - endpoint: 'GET /api/apm/backends/{backendName}/upstream_services', + endpoint: 'GET /internal/apm/backends/{backendName}/upstream_services', params: { path: { backendName, diff --git a/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_error_rate_chart.tsx b/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_error_rate_chart.tsx index d48178a8522be..cf14145dba82a 100644 --- a/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_error_rate_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_error_rate_chart.tsx @@ -44,7 +44,7 @@ export function BackendFailedTransactionRateChart({ } return callApmApi({ - endpoint: 'GET /api/apm/backends/{backendName}/charts/error_rate', + endpoint: 'GET /internal/apm/backends/{backendName}/charts/error_rate', params: { path: { backendName, diff --git a/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_latency_chart.tsx b/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_latency_chart.tsx index 759d153988875..3f5a56d55d823 100644 --- a/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_latency_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_latency_chart.tsx @@ -40,7 +40,7 @@ export function BackendLatencyChart({ height }: { height: number }) { } return callApmApi({ - endpoint: 'GET /api/apm/backends/{backendName}/charts/latency', + endpoint: 'GET /internal/apm/backends/{backendName}/charts/latency', params: { path: { backendName, diff --git a/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_throughput_chart.tsx index 2cfc7ea317628..f5d9cb7a7a55e 100644 --- a/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/backend_detail_overview/backend_throughput_chart.tsx @@ -36,7 +36,7 @@ export function BackendThroughputChart({ height }: { height: number }) { } return callApmApi({ - endpoint: 'GET /api/apm/backends/{backendName}/charts/throughput', + endpoint: 'GET /internal/apm/backends/{backendName}/charts/throughput', params: { path: { backendName, diff --git a/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx index ea135104982e5..05eb9892fc108 100644 --- a/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx @@ -45,7 +45,7 @@ export function BackendInventoryDependenciesTable() { } return callApmApi({ - endpoint: 'GET /api/apm/backends/top_backends', + endpoint: 'GET /internal/apm/backends/top_backends', params: { query: { start, end, environment, numBuckets: 20, offset, kuery }, }, diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx index b6fc0d4fcf65d..3d1d0ee564ba4 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx @@ -35,7 +35,7 @@ const ALERT_RULE_TYPE_ID: typeof ALERT_RULE_TYPE_ID_TYPED = ALERT_RULE_TYPE_ID_NON_TYPED; type ErrorDistributionAPIResponse = - APIReturnType<'GET /api/apm/services/{serviceName}/errors/distribution'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/distribution'>; interface FormattedBucket { x0: number; diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx index 6e6f323a5525a..d4ffd8ece9d4d 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx @@ -54,7 +54,7 @@ const TransactionLinkName = euiStyled.div` `; interface Props { - errorGroup: APIReturnType<'GET /api/apm/services/{serviceName}/errors/{groupId}'>; + errorGroup: APIReturnType<'GET /internal/apm/services/{serviceName}/errors/{groupId}'>; urlParams: ApmUrlParams; kuery: string; } diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx index 9145e019c37ea..0114348892984 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx @@ -127,7 +127,7 @@ export function ErrorGroupDetails() { (callApmApi) => { if (start && end) { return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/{groupId}', + endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/List/index.tsx index d42c77c06b0ab..d7c5b1f4bc358 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/index.tsx @@ -48,7 +48,7 @@ const Culprit = euiStyled.div` `; type ErrorGroupItem = - APIReturnType<'GET /api/apm/services/{serviceName}/errors'>['errorGroups'][0]; + APIReturnType<'GET /internal/apm/services/{serviceName}/errors'>['errorGroups'][0]; interface Props { items: ErrorGroupItem[]; diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx index 97a3c38b65986..a445b0d8522c5 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx @@ -44,7 +44,7 @@ export function ErrorGroupOverview() { if (start && end) { return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors', + endpoint: 'GET /internal/apm/services/{serviceName}/errors', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/components/app/service_dependencies/service_dependencies_breakdown_chart.tsx b/x-pack/plugins/apm/public/components/app/service_dependencies/service_dependencies_breakdown_chart.tsx index 426328a8ce9f0..979a2404dfdf0 100644 --- a/x-pack/plugins/apm/public/components/app/service_dependencies/service_dependencies_breakdown_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_dependencies/service_dependencies_breakdown_chart.tsx @@ -29,7 +29,8 @@ export function ServiceDependenciesBreakdownChart({ const { data, status } = useFetcher( (callApmApi) => { return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/dependencies/breakdown', + endpoint: + 'GET /internal/apm/services/{serviceName}/dependencies/breakdown', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index 7cd9c7c8e199e..aea7c1faab601 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -64,7 +64,7 @@ function useServicesFetcher() { (callApmApi) => { if (start && end) { return callApmApi({ - endpoint: 'GET /api/apm/services', + endpoint: 'GET /internal/apm/services', params: { query: { environment, @@ -90,7 +90,7 @@ function useServicesFetcher() { (callApmApi) => { if (start && end && mainStatisticsData.items.length) { return callApmApi({ - endpoint: 'GET /api/apm/services/detailed_statistics', + endpoint: 'GET /internal/apm/services/detailed_statistics', params: { query: { environment, diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/__fixtures__/service_api_mock_data.ts b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/__fixtures__/service_api_mock_data.ts index 0f5edb5a4c9ce..85e7eeadc7487 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/__fixtures__/service_api_mock_data.ts +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/__fixtures__/service_api_mock_data.ts @@ -7,7 +7,7 @@ import { APIReturnType } from '../../../../../services/rest/createCallApmApi'; -type ServiceListAPIResponse = APIReturnType<'GET /api/apm/services'>; +type ServiceListAPIResponse = APIReturnType<'GET /internal/apm/services'>; export const items: ServiceListAPIResponse['items'] = [ { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx index 9c1893b76f7db..ea65c837a4177 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx @@ -43,10 +43,10 @@ import { ServiceLink } from '../../../shared/service_link'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { HealthBadge } from './HealthBadge'; -type ServiceListAPIResponse = APIReturnType<'GET /api/apm/services'>; +type ServiceListAPIResponse = APIReturnType<'GET /internal/apm/services'>; type Items = ServiceListAPIResponse['items']; type ServicesDetailedStatisticsAPIResponse = - APIReturnType<'GET /api/apm/services/detailed_statistics'>; + APIReturnType<'GET /internal/apm/services/detailed_statistics'>; type ServiceListItem = ValuesType; diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/service_list.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/service_list.test.tsx index 75aad2283de0b..69ec1e6b1eb93 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/service_list.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/service_list.test.tsx @@ -33,7 +33,7 @@ describe('ServiceList', () => { const callApmApiSpy = getCallApmApiSpy().mockImplementation( ({ endpoint }) => { - if (endpoint === 'GET /api/apm/fallback_to_transactions') { + if (endpoint === 'GET /internal/apm/fallback_to_transactions') { return Promise.resolve({ fallbackToTransactions: false }); } return Promise.reject(`Response for ${endpoint} is not defined`); diff --git a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx index 79818473d26b1..bb32919196f84 100644 --- a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx @@ -35,7 +35,7 @@ export function ServiceLogs() { (callApmApi) => { if (start && end) { return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/infrastructure', + endpoint: 'GET /internal/apm/services/{serviceName}/infrastructure', params: { path: { serviceName }, query: { @@ -92,7 +92,7 @@ export function ServiceLogs() { } export const getInfrastructureKQLFilter = ( - data?: APIReturnType<'GET /api/apm/services/{serviceName}/infrastructure'> + data?: APIReturnType<'GET /internal/apm/services/{serviceName}/infrastructure'> ) => { const containerIds = data?.serviceInfrastructure?.containerIds ?? []; const hostNames = data?.serviceInfrastructure?.hostNames ?? []; diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx index c01cf4579fdbd..c04619338f80b 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx @@ -38,7 +38,7 @@ export function BackendContents({ (callApmApi) => { if (backendName) { return callApmApi({ - endpoint: 'GET /api/apm/service-map/backend/{backendName}', + endpoint: 'GET /internal/apm/service-map/backend/{backendName}', params: { path: { backendName }, query: { diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/service_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/service_contents.tsx index 5eef580793d10..f320123ce0723 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/service_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/service_contents.tsx @@ -47,7 +47,7 @@ export function ServiceContents({ (callApmApi) => { if (serviceName && start && end) { return callApmApi({ - endpoint: 'GET /api/apm/service-map/service/{serviceName}', + endpoint: 'GET /internal/apm/service-map/service/{serviceName}', params: { path: { serviceName }, query: { environment, start, end }, diff --git a/x-pack/plugins/apm/public/components/app/service_map/index.tsx b/x-pack/plugins/apm/public/components/app/service_map/index.tsx index 7499eb9cd658c..75d5837f738c5 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/index.tsx @@ -124,7 +124,7 @@ export function ServiceMap({ return callApmApi({ isCachable: false, - endpoint: 'GET /api/apm/service-map', + endpoint: 'GET /internal/apm/service-map', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx index 1f83ad317e962..19527cd084989 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx @@ -85,7 +85,7 @@ export function ServiceNodeMetrics() { if (start && end) { return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/node/{serviceNodeName}/metadata', + 'GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata', params: { path: { serviceName, serviceNodeName }, query: { 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 bef87891d7416..04bb578b0c434 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 @@ -48,7 +48,7 @@ function ServiceNodeOverview() { return undefined; } return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/serviceNodes', + endpoint: 'GET /internal/apm/services/{serviceName}/serviceNodes', params: { path: { serviceName, 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 62cefa5de47fa..42a0c68535efe 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 @@ -99,21 +99,21 @@ describe('ServiceOverview', () => { /* eslint-disable @typescript-eslint/naming-convention */ const calls = { - 'GET /api/apm/services/{serviceName}/error_groups/main_statistics': { + 'GET /internal/apm/services/{serviceName}/error_groups/main_statistics': { error_groups: [] as any[], }, - 'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics': + 'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics': { transactionGroups: [] as any[], totalTransactionGroups: 0, isAggregationAccurate: true, }, - 'GET /api/apm/services/{serviceName}/dependencies': { + 'GET /internal/apm/services/{serviceName}/dependencies': { serviceDependencies: [], }, - 'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics': + 'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics': [], - 'GET /api/apm/services/{serviceName}/transactions/charts/latency': { + 'GET /internal/apm/services/{serviceName}/transactions/charts/latency': { currentPeriod: { overallAvgDuration: null, latencyTimeseries: [], @@ -123,26 +123,27 @@ describe('ServiceOverview', () => { latencyTimeseries: [], }, }, - 'GET /api/apm/services/{serviceName}/throughput': { + 'GET /internal/apm/services/{serviceName}/throughput': { currentPeriod: [], previousPeriod: [], }, - 'GET /api/apm/services/{serviceName}/transactions/charts/error_rate': { - currentPeriod: { - transactionErrorRate: [], - noHits: true, - average: null, - }, - previousPeriod: { - transactionErrorRate: [], - noHits: true, - average: null, + 'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate': + { + currentPeriod: { + transactionErrorRate: [], + noHits: true, + average: null, + }, + previousPeriod: { + transactionErrorRate: [], + noHits: true, + average: null, + }, }, - }, 'GET /api/apm/services/{serviceName}/annotation/search': { annotations: [], }, - 'GET /api/apm/fallback_to_transactions': { + 'GET /internal/apm/fallback_to_transactions': { fallbackToTransactions: false, }, }; 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 b035d626c371a..b29daa1dd5585 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 @@ -59,7 +59,7 @@ export function ServiceOverviewDependenciesTable({ } return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/dependencies', + endpoint: 'GET /internal/apm/services/{serviceName}/dependencies', params: { path: { serviceName }, query: { start, end, environment, numBuckets: 20, offset }, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_columns.tsx index 86f1907365bf6..14a8b59cd7826 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_columns.tsx @@ -16,9 +16,9 @@ import { TimestampTooltip } from '../../../shared/TimestampTooltip'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; type ErrorGroupMainStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>; type ErrorGroupDetailedStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/detailed_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>; export function getColumns({ serviceName, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index ebf634fd6b186..d711676378039 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -30,9 +30,9 @@ interface Props { serviceName: string; } type ErrorGroupMainStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>; type ErrorGroupDetailedStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/detailed_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>; type SortDirection = 'asc' | 'desc'; type SortField = 'name' | 'lastSeen' | 'occurrences'; @@ -97,7 +97,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { } return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/error_groups/main_statistics', + 'GET /internal/apm/services/{serviceName}/error_groups/main_statistics', params: { path: { serviceName }, query: { @@ -150,7 +150,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { if (requestId && items.length && start && end && transactionType) { return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/error_groups/detailed_statistics', + 'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics', params: { path: { serviceName }, query: { 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 ab42d9b80634a..74a9a60afd915 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 @@ -28,9 +28,9 @@ interface ServiceOverviewInstancesChartAndTableProps { } type ApiResponseMainStats = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics'>; type ApiResponseDetailedStats = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; const INITIAL_STATE_MAIN_STATS = { currentPeriodItems: [] as ApiResponseMainStats['currentPeriod'], @@ -100,7 +100,7 @@ export function ServiceOverviewInstancesChartAndTable({ return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics', + 'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics', params: { path: { serviceName, @@ -181,7 +181,7 @@ export function ServiceOverviewInstancesChartAndTable({ return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics', + 'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics', params: { path: { serviceName, 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 index 3a80e0b075323..853ea37112ad8 100644 --- 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 @@ -33,11 +33,11 @@ import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { InstanceActionsMenu } from './instance_actions_menu'; type ServiceInstanceMainStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics'>; type MainStatsServiceInstanceItem = ServiceInstanceMainStatistics['currentPeriod'][0]; type ServiceInstanceDetailedStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; export function getColumns({ serviceName, 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 a8a93e8d4473e..9084ffdda59f8 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 @@ -29,11 +29,11 @@ import { useApmParams } from '../../../../hooks/use_apm_params'; import { useBreakpoints } from '../../../../hooks/use_breakpoints'; type ServiceInstanceMainStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics'>; type MainStatsServiceInstanceItem = ServiceInstanceMainStatistics['currentPeriod'][0]; type ServiceInstanceDetailedStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; export interface TableOptions { pageIndex: number; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts index cd7e64ae39668..7e7f30065c958 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts @@ -17,7 +17,7 @@ import { } from '../../../../shared/transaction_action_menu/sections_helper'; type InstaceDetails = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>; function getInfraMetricsQuery(timestamp?: string) { if (!timestamp) { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_details.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_details.test.tsx index 3a0ed21ca9808..b8e18eec43e6a 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_details.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_details.test.tsx @@ -16,7 +16,7 @@ import { InstanceDetails } from './intance_details'; import * as useInstanceDetailsFetcher from './use_instance_details_fetcher'; type ServiceInstanceDetails = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>; describe('InstanceDetails', () => { it('renders loading spinner when data is being fetched', () => { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx index 7a194039e0d4c..fad5628b2192d 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx @@ -34,7 +34,7 @@ import { getCloudIcon, getContainerIcon } from '../../../shared/service_icons'; import { useInstanceDetailsFetcher } from './use_instance_details_fetcher'; type ServiceInstanceDetails = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>; interface Props { serviceName: string; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/use_instance_details_fetcher.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/use_instance_details_fetcher.tsx index c1018d6d742fa..de833b9b3be11 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/use_instance_details_fetcher.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/use_instance_details_fetcher.tsx @@ -28,7 +28,7 @@ export function useInstanceDetailsFetcher({ } return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}', + 'GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx index c3d17b9f18a98..0780c2d272715 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx @@ -69,7 +69,7 @@ export function ServiceOverviewThroughputChart({ (callApmApi) => { if (serviceName && transactionType && start && end) { return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/throughput', + endpoint: 'GET /internal/apm/services/{serviceName}/throughput', params: { path: { serviceName, 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 15caa833764b9..5342ae8e0db28 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,7 +19,7 @@ import { ServiceProfilingFlamegraph } from './service_profiling_flamegraph'; import { ServiceProfilingTimeline } from './service_profiling_timeline'; type ApiResponse = - APIReturnType<'GET /api/apm/services/{serviceName}/profiling/timeline'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/profiling/timeline'>; const DEFAULT_DATA: ApiResponse = { profilingTimeline: [] }; export function ServiceProfiling() { @@ -38,7 +38,7 @@ export function ServiceProfiling() { } return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/profiling/timeline', + endpoint: 'GET /internal/apm/services/{serviceName}/profiling/timeline', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/components/app/service_profiling/service_profiling_flamegraph.tsx b/x-pack/plugins/apm/public/components/app/service_profiling/service_profiling_flamegraph.tsx index a2ce4dcf7e83f..8626c4a3b061c 100644 --- a/x-pack/plugins/apm/public/components/app/service_profiling/service_profiling_flamegraph.tsx +++ b/x-pack/plugins/apm/public/components/app/service_profiling/service_profiling_flamegraph.tsx @@ -147,7 +147,8 @@ export function ServiceProfilingFlamegraph({ } return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/profiling/statistics', + endpoint: + 'GET /internal/apm/services/{serviceName}/profiling/statistics', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx index 63cd27cba41e8..9725df9809ea0 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx @@ -16,7 +16,7 @@ import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge'; import { useTimeRange } from '../../../hooks/use_time_range'; -type TracesAPIResponse = APIReturnType<'GET /api/apm/traces'>; +type TracesAPIResponse = APIReturnType<'GET /internal/apm/traces'>; const DEFAULT_RESPONSE: TracesAPIResponse = { items: [], }; @@ -35,7 +35,7 @@ export function TraceOverview() { (callApmApi) => { if (start && end) { return callApmApi({ - endpoint: 'GET /api/apm/traces', + endpoint: 'GET /internal/apm/traces', params: { query: { environment, diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx index 7c4e0ffca1f58..0fc25b28b60e8 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/trace_list.tsx @@ -20,7 +20,7 @@ import { ImpactBar } from '../../shared/ImpactBar'; import { TransactionDetailLink } from '../../shared/Links/apm/transaction_detail_link'; import { ITableColumn, ManagedTable } from '../../shared/managed_table'; -type TraceGroup = APIReturnType<'GET /api/apm/traces'>['items'][0]; +type TraceGroup = APIReturnType<'GET /internal/apm/traces'>['items'][0]; const StyledTransactionLink = euiStyled(TransactionDetailLink)` font-size: ${({ theme }) => theme.eui.euiFontSizeS}; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts b/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts index 12bb8dbe12e4b..e7fbc315522e4 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_details/use_waterfall_fetcher.ts @@ -36,7 +36,7 @@ export function useWaterfallFetcher() { (callApmApi) => { if (traceId && start && end) { return callApmApi({ - endpoint: 'GET /api/apm/traces/{traceId}', + endpoint: 'GET /internal/apm/traces/{traceId}', params: { path: { traceId }, query: { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/waterfall_helpers.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/waterfall_helpers.ts index 9501ad1839d46..661ba2556d84e 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/waterfall_helpers.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/Waterfall/waterfall_helpers/waterfall_helpers.ts @@ -12,7 +12,7 @@ import { APMError } from '../../../../../../../../typings/es_schemas/ui/apm_erro import { Span } from '../../../../../../../../typings/es_schemas/ui/span'; import { Transaction } from '../../../../../../../../typings/es_schemas/ui/transaction'; -type TraceAPIResponse = APIReturnType<'GET /api/apm/traces/{traceId}'>; +type TraceAPIResponse = APIReturnType<'GET /internal/apm/traces/{traceId}'>; interface IWaterfallGroup { [key: string]: IWaterfallSpanOrTransaction[]; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfallContainer.stories.data.ts b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfallContainer.stories.data.ts index 87150cdfc83a8..60285c835bbf3 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfallContainer.stories.data.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfallContainer.stories.data.ts @@ -16,7 +16,7 @@ export const location = { hash: '', } as Location; -type TraceAPIResponse = APIReturnType<'GET /api/apm/traces/{traceId}'>; +type TraceAPIResponse = APIReturnType<'GET /internal/apm/traces/{traceId}'>; export const urlParams = { start: '2020-03-22T15:16:38.742Z', diff --git a/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx index 468a90f6b17de..251daf3d0d032 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_link/index.tsx @@ -28,7 +28,7 @@ export function TransactionLink() { (callApmApi) => { if (transactionId) { return callApmApi({ - endpoint: 'GET /api/apm/transactions/{transactionId}', + endpoint: 'GET /internal/apm/transactions/{transactionId}', params: { path: { transactionId, diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx index ac0b8a1517b20..14bf7de789c98 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx @@ -49,7 +49,7 @@ export function ApmMainTemplate({ services.observability.navigation.PageTemplate; const { data } = useFetcher((callApmApi) => { - return callApmApi({ endpoint: 'GET /api/apm/has_data' }); + return callApmApi({ endpoint: 'GET /internal/apm/has_data' }); }, []); const noDataConfig = getNoDataConfig({ diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx index f6ffc34ecee02..a0cf2a1aec485 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx @@ -20,7 +20,7 @@ export function ErrorMetadata({ error }: Props) { const { data: errorEvent, status } = useFetcher( (callApmApi) => { return callApmApi({ - endpoint: 'GET /api/apm/event_metadata/{processorEvent}/{id}', + endpoint: 'GET /internal/apm/event_metadata/{processorEvent}/{id}', params: { path: { processorEvent: ProcessorEvent.error, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx index bf5702b4acf3e..166440d0975b0 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx @@ -20,7 +20,7 @@ export function SpanMetadata({ span }: Props) { const { data: spanEvent, status } = useFetcher( (callApmApi) => { return callApmApi({ - endpoint: 'GET /api/apm/event_metadata/{processorEvent}/{id}', + endpoint: 'GET /internal/apm/event_metadata/{processorEvent}/{id}', params: { path: { processorEvent: ProcessorEvent.span, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx index 32c0101c73b4d..b437173c4b632 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx @@ -20,7 +20,7 @@ export function TransactionMetadata({ transaction }: Props) { const { data: transactionEvent, status } = useFetcher( (callApmApi) => { return callApmApi({ - endpoint: 'GET /api/apm/event_metadata/{processorEvent}/{id}', + endpoint: 'GET /internal/apm/event_metadata/{processorEvent}/{id}', params: { path: { processorEvent: ProcessorEvent.transaction, diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx index c84cbc9a4b104..970cc886d30bd 100644 --- a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx @@ -27,7 +27,7 @@ import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { getAPMHref } from '../Links/apm/APMLink'; export type AnomalyDetectionApiResponse = - APIReturnType<'GET /api/apm/settings/anomaly-detection/jobs'>; + APIReturnType<'GET /internal/apm/settings/anomaly-detection/jobs'>; const DEFAULT_DATA = { jobs: [], hasLegacyJobs: false }; diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx index e63f7fbc79ab2..4573e8e5a174f 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx @@ -30,7 +30,7 @@ import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { getAlertAnnotations } from './get_alert_annotations'; type Alert = ValuesType< - APIReturnType<'GET /api/apm/services/{serviceName}/alerts'>['alerts'] + APIReturnType<'GET /internal/apm/services/{serviceName}/alerts'>['alerts'] >; const euiColorDanger = 'red'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx index 39e1ce59de4bc..f4c47a9247e75 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx @@ -46,7 +46,7 @@ const ALERT_RULE_TYPE_ID: typeof ALERT_RULE_TYPE_ID_TYPED = const ALERT_RULE_NAME: typeof ALERT_RULE_NAME_TYPED = ALERT_RULE_NAME_NON_TYPED; type Alert = ValuesType< - APIReturnType<'GET /api/apm/services/{serviceName}/alerts'>['alerts'] + APIReturnType<'GET /internal/apm/services/{serviceName}/alerts'>['alerts'] >; function getAlertColor({ diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx index d39770b68d5b6..56138a676a29d 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx @@ -12,7 +12,7 @@ import { getDurationFormatter } from '../../../../../common/utils/formatters'; import { CustomTooltip } from './custom_tooltip'; type ServiceInstanceMainStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics'>; type MainStatsServiceInstanceItem = ServiceInstanceMainStatistics['currentPeriod'][0]; diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.tsx index 7289ffddd0eab..2974f909615f4 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.tsx @@ -18,7 +18,7 @@ import { import { useTheme } from '../../../../hooks/use_theme'; type ServiceInstanceMainStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics'>; type MainStatsServiceInstanceItem = ServiceInstanceMainStatistics['currentPeriod'][0]; 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 5c974664eb1d3..30bf5908ef376 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 @@ -37,7 +37,7 @@ import { getResponseTimeTickFormatter } from '../transaction_charts/helper'; import { CustomTooltip } from './custom_tooltip'; type ApiResponseMainStats = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics'>; export interface InstancesLatencyDistributionChartProps { height: number; diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx index 511b605e3c22c..ad51e66f1959c 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx @@ -42,8 +42,8 @@ import { import { LatencyChart } from './'; interface Args { - alertsResponse: APIReturnType<'GET /api/apm/services/{serviceName}/alerts'>; - latencyChartResponse: APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/latency'>; + alertsResponse: APIReturnType<'GET /internal/apm/services/{serviceName}/alerts'>; + latencyChartResponse: APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/charts/latency'>; } export default { @@ -70,7 +70,7 @@ export default { basePath: { prepend: () => {} }, get: (endpoint: string) => { switch (endpoint) { - case `/api/apm/services/${serviceName}/transactions/charts/latency`: + case `/internal/apm/services/${serviceName}/transactions/charts/latency`: return latencyChartResponse; default: return {}; diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts index e814459fbf519..079b8455de7ad 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts @@ -39,7 +39,7 @@ export function useTransactionBreakdown({ if (serviceName && start && end && transactionType) { return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/transaction/charts/breakdown', + 'GET /internal/apm/services/{serviceName}/transaction/charts/breakdown', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index 88788c7c3d39f..95b73a5276b8a 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -36,7 +36,7 @@ interface Props { } type ErrorRate = - APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/error_rate'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate'>; const INITIAL_STATE: ErrorRate = { currentPeriod: { @@ -82,7 +82,7 @@ export function TransactionErrorRateChart({ if (transactionType && serviceName && start && end) { return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/charts/error_rate', + 'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/components/shared/service_icons/cloud_details.tsx b/x-pack/plugins/apm/public/components/shared/service_icons/cloud_details.tsx index 2c706fa863a93..eded3ff9fd1c6 100644 --- a/x-pack/plugins/apm/public/components/shared/service_icons/cloud_details.tsx +++ b/x-pack/plugins/apm/public/components/shared/service_icons/cloud_details.tsx @@ -12,7 +12,7 @@ import React from 'react'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; type ServiceDetailsReturnType = - APIReturnType<'GET /api/apm/services/{serviceName}/metadata/details'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/metadata/details'>; interface Props { cloud: ServiceDetailsReturnType['cloud']; diff --git a/x-pack/plugins/apm/public/components/shared/service_icons/container_details.tsx b/x-pack/plugins/apm/public/components/shared/service_icons/container_details.tsx index 75f7c23a78085..cd2e3a518b3b3 100644 --- a/x-pack/plugins/apm/public/components/shared/service_icons/container_details.tsx +++ b/x-pack/plugins/apm/public/components/shared/service_icons/container_details.tsx @@ -13,7 +13,7 @@ import { asInteger } from '../../../../common/utils/formatters'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; type ServiceDetailsReturnType = - APIReturnType<'GET /api/apm/services/{serviceName}/metadata/details'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/metadata/details'>; interface Props { container: ServiceDetailsReturnType['container']; diff --git a/x-pack/plugins/apm/public/components/shared/service_icons/index.test.tsx b/x-pack/plugins/apm/public/components/shared/service_icons/index.test.tsx index bd90cae0277af..b04fe26e1a8bc 100644 --- a/x-pack/plugins/apm/public/components/shared/service_icons/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/service_icons/index.test.tsx @@ -186,7 +186,7 @@ describe('ServiceIcons', () => { }; it('Shows loading spinner while fetching data', () => { const apisMockData = { - 'GET /api/apm/services/{serviceName}/metadata/icons': { + 'GET /internal/apm/services/{serviceName}/metadata/icons': { data: { agentName: 'java', containerType: 'Kubernetes', @@ -195,7 +195,7 @@ describe('ServiceIcons', () => { status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), }, - 'GET /api/apm/services/{serviceName}/metadata/details': { + 'GET /internal/apm/services/{serviceName}/metadata/details': { data: undefined, status: fetcherHook.FETCH_STATUS.LOADING, refetch: jest.fn(), @@ -228,7 +228,7 @@ describe('ServiceIcons', () => { it('shows service content', () => { const apisMockData = { - 'GET /api/apm/services/{serviceName}/metadata/icons': { + 'GET /internal/apm/services/{serviceName}/metadata/icons': { data: { agentName: 'java', containerType: 'Kubernetes', @@ -237,7 +237,7 @@ describe('ServiceIcons', () => { status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), }, - 'GET /api/apm/services/{serviceName}/metadata/details': { + 'GET /internal/apm/services/{serviceName}/metadata/details': { data: { service: { versions: ['v1.0.0'] } }, status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/shared/service_icons/index.tsx b/x-pack/plugins/apm/public/components/shared/service_icons/index.tsx index 780c3b0222f13..77639ea1f6d72 100644 --- a/x-pack/plugins/apm/public/components/shared/service_icons/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/service_icons/index.tsx @@ -71,7 +71,7 @@ export function ServiceIcons({ start, end, serviceName }: Props) { (callApmApi) => { if (serviceName && start && end) { return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/metadata/icons', + endpoint: 'GET /internal/apm/services/{serviceName}/metadata/icons', params: { path: { serviceName }, query: { start, end }, @@ -87,7 +87,7 @@ export function ServiceIcons({ start, end, serviceName }: Props) { if (selectedIconPopover && serviceName && start && end) { return callApmApi({ isCachable: true, - endpoint: 'GET /api/apm/services/{serviceName}/metadata/details', + endpoint: 'GET /internal/apm/services/{serviceName}/metadata/details', params: { path: { serviceName }, query: { start, end }, diff --git a/x-pack/plugins/apm/public/components/shared/service_icons/service_details.tsx b/x-pack/plugins/apm/public/components/shared/service_icons/service_details.tsx index daf253f6bc0a9..9ab8b5ba8a1f0 100644 --- a/x-pack/plugins/apm/public/components/shared/service_icons/service_details.tsx +++ b/x-pack/plugins/apm/public/components/shared/service_icons/service_details.tsx @@ -12,7 +12,7 @@ import React from 'react'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; type ServiceDetailsReturnType = - APIReturnType<'GET /api/apm/services/{serviceName}/metadata/details'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/metadata/details'>; interface Props { service: ServiceDetailsReturnType['service']; diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/index.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/index.tsx index bc234b88a08e4..ea247d1907a26 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/custom_link_menu_section/index.tsx @@ -61,7 +61,7 @@ export function CustomLinkMenuSection({ (callApmApi) => callApmApi({ isCachable: false, - endpoint: 'GET /api/apm/settings/custom_links', + endpoint: 'GET /internal/apm/settings/custom_links', params: { query: convertFiltersToQuery(filters) }, }), [filters] diff --git a/x-pack/plugins/apm/public/components/shared/transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/shared/transactions_table/get_columns.tsx index b11a2994f1caa..18e9beb2c8795 100644 --- a/x-pack/plugins/apm/public/components/shared/transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/shared/transactions_table/get_columns.tsx @@ -28,13 +28,13 @@ import { TruncateWithTooltip } from '../truncate_with_tooltip'; import { getLatencyColumnLabel } from './get_latency_column_label'; type TransactionGroupMainStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics'>; type ServiceTransactionGroupItem = ValuesType< TransactionGroupMainStatistics['transactionGroups'] >; type TransactionGroupDetailedStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/detailed_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics'>; export function getColumns({ serviceName, diff --git a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx index 6f91684de0ee5..d6d955b8ef5ab 100644 --- a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -30,7 +30,7 @@ import { ElasticDocsLink } from '../Links/ElasticDocsLink'; import { useBreakpoints } from '../../../hooks/use_breakpoints'; type ApiResponse = - APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics'>; interface InitialState { requestId: string; @@ -116,7 +116,7 @@ export function TransactionsTable({ } return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics', + 'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics', params: { path: { serviceName }, query: { @@ -189,7 +189,7 @@ export function TransactionsTable({ ) { return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/detailed_statistics', + 'GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx b/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx index b8d6feda826a5..bf9f2941fa2fb 100644 --- a/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx +++ b/x-pack/plugins/apm/public/context/anomaly_detection_jobs/anomaly_detection_jobs_context.tsx @@ -10,7 +10,7 @@ import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher'; import { APIReturnType } from '../../services/rest/createCallApmApi'; export interface AnomalyDetectionJobsContextValue { - anomalyDetectionJobsData?: APIReturnType<'GET /api/apm/settings/anomaly-detection/jobs'>; + anomalyDetectionJobsData?: APIReturnType<'GET /internal/apm/settings/anomaly-detection/jobs'>; anomalyDetectionJobsStatus: FETCH_STATUS; anomalyDetectionJobsRefetch: () => void; } @@ -30,7 +30,7 @@ export function AnomalyDetectionJobsContextProvider({ const { data, status } = useFetcher( (callApmApi) => callApmApi({ - endpoint: `GET /api/apm/settings/anomaly-detection/jobs`, + endpoint: `GET /internal/apm/settings/anomaly-detection/jobs`, }), [fetchId], // eslint-disable-line react-hooks/exhaustive-deps { showToastOnError: false } diff --git a/x-pack/plugins/apm/public/context/apm_backend/apm_backend_context.tsx b/x-pack/plugins/apm/public/context/apm_backend/apm_backend_context.tsx index d6cc139e72b64..6093f05c2cb02 100644 --- a/x-pack/plugins/apm/public/context/apm_backend/apm_backend_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_backend/apm_backend_context.tsx @@ -15,7 +15,7 @@ export const ApmBackendContext = createContext< | { backendName: string; metadata: { - data?: APIReturnType<'GET /api/apm/backends/{backendName}/metadata'>; + data?: APIReturnType<'GET /internal/apm/backends/{backendName}/metadata'>; status?: FETCH_STATUS; }; } @@ -41,7 +41,7 @@ export function ApmBackendContextProvider({ } return callApmApi({ - endpoint: 'GET /api/apm/backends/{backendName}/metadata', + endpoint: 'GET /internal/apm/backends/{backendName}/metadata', params: { path: { backendName, diff --git a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx index d6b052a5dc884..9d207eee2fbaa 100644 --- a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx @@ -20,7 +20,7 @@ import { useApmParams } from '../../hooks/use_apm_params'; import { useTimeRange } from '../../hooks/use_time_range'; export type APMServiceAlert = ValuesType< - APIReturnType<'GET /api/apm/services/{serviceName}/alerts'>['alerts'] + APIReturnType<'GET /internal/apm/services/{serviceName}/alerts'>['alerts'] >; export const APMServiceContext = createContext<{ diff --git a/x-pack/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts index e30fd962e5a92..0202ff3a10123 100644 --- a/x-pack/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts @@ -29,7 +29,7 @@ export function useServiceAgentFetcher({ (callApmApi) => { if (serviceName) { return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/agent', + endpoint: 'GET /internal/apm/services/{serviceName}/agent', params: { path: { serviceName }, query: { start, end }, diff --git a/x-pack/plugins/apm/public/context/apm_service/use_service_alerts_fetcher.tsx b/x-pack/plugins/apm/public/context/apm_service/use_service_alerts_fetcher.tsx index 8491cdfb3f5eb..d0e1edc775a81 100644 --- a/x-pack/plugins/apm/public/context/apm_service/use_service_alerts_fetcher.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_alerts_fetcher.tsx @@ -41,7 +41,7 @@ export function useServiceAlertsFetcher({ } return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/alerts', + endpoint: 'GET /internal/apm/services/{serviceName}/alerts', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx index c2e81cb0c92ae..e81a2d956bf3c 100644 --- a/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx @@ -22,7 +22,8 @@ export function useServiceTransactionTypesFetcher({ (callApmApi) => { if (serviceName && start && end) { return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/transaction_types', + endpoint: + 'GET /internal/apm/services/{serviceName}/transaction_types', params: { path: { serviceName }, query: { start, end }, diff --git a/x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts b/x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts index 9c637dc1336ad..dc19af0a98cea 100644 --- a/x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts +++ b/x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts @@ -10,7 +10,7 @@ import { useFetcher } from './use_fetcher'; export function useDynamicIndexPatternFetcher() { const { data, status } = useFetcher((callApmApi) => { return callApmApi({ - endpoint: 'GET /api/apm/index_pattern/dynamic', + endpoint: 'GET /internal/apm/index_pattern/dynamic', isCachable: true, }); }, []); diff --git a/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx index d6352b561c641..e37677e806e8a 100644 --- a/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx @@ -38,7 +38,7 @@ export function useEnvironmentsFetcher({ (callApmApi) => { if (start && end) { return callApmApi({ - endpoint: 'GET /api/apm/environments', + endpoint: 'GET /internal/apm/environments', params: { query: { start, diff --git a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx index 683a545cdd125..120cbba952f3e 100644 --- a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx @@ -30,7 +30,8 @@ export function useErrorGroupDistributionFetcher({ (callApmApi) => { if (start && end) { return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', + endpoint: + 'GET /internal/apm/services/{serviceName}/errors/distribution', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/hooks/use_fallback_to_transactions_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fallback_to_transactions_fetcher.tsx index 35122bfef1749..3dfb02cec5fbc 100644 --- a/x-pack/plugins/apm/public/hooks/use_fallback_to_transactions_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fallback_to_transactions_fetcher.tsx @@ -19,7 +19,7 @@ export function useFallbackToTransactionsFetcher({ kuery }: { kuery: string }) { const { data = { fallbackToTransactions: false } } = useFetcher( (callApmApi) => { return callApmApi({ - endpoint: 'GET /api/apm/fallback_to_transactions', + endpoint: 'GET /internal/apm/fallback_to_transactions', params: { query: { kuery, start, end }, }, diff --git a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts index 82338d45a4133..ef360698192e1 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts @@ -40,7 +40,7 @@ export function useServiceMetricChartsFetcher({ (callApmApi) => { if (serviceName && start && end && agentName) { return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/metrics/charts', + endpoint: 'GET /internal/apm/services/{serviceName}/metrics/charts', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts index 660408700b4df..d44ec853a242c 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts @@ -57,7 +57,7 @@ export function useTransactionLatencyChartsFetcher({ ) { return callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/charts/latency', + 'GET /internal/apm/services/{serviceName}/transactions/charts/latency', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts index 5279c7ce143fc..48c2d555fd43b 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts @@ -51,7 +51,7 @@ export function useTransactionTraceSamplesFetcher({ if (serviceName && start && end && transactionType && transactionName) { const response = await callApmApi({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/traces/samples', + 'GET /internal/apm/services/{serviceName}/transactions/traces/samples', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts b/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts index 1fb4eb51dd770..bb68143089347 100644 --- a/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts +++ b/x-pack/plugins/apm/public/selectors/latency_chart_selectors.ts @@ -14,7 +14,7 @@ import { APMChartSpec, Coordinate } from '../../typings/timeseries'; import { APIReturnType } from '../services/rest/createCallApmApi'; export type LatencyChartsResponse = - APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/latency'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/charts/latency'>; export interface LatencyChartData { currentPeriod?: APMChartSpec; diff --git a/x-pack/plugins/apm/public/services/callApi.test.ts b/x-pack/plugins/apm/public/services/callApi.test.ts index 82be0bbf5dfcf..85e58bc81a93f 100644 --- a/x-pack/plugins/apm/public/services/callApi.test.ts +++ b/x-pack/plugins/apm/public/services/callApi.test.ts @@ -41,11 +41,14 @@ describe('callApi', () => { }); it('should add debug param for APM endpoints', async () => { - await callApi(core, { pathname: `/api/apm/status/server` }); - - expect(core.http.get).toHaveBeenCalledWith('/api/apm/status/server', { - query: { _inspect: true }, - }); + await callApi(core, { pathname: `/internal/apm/status/server` }); + + expect(core.http.get).toHaveBeenCalledWith( + '/internal/apm/status/server', + { + query: { _inspect: true }, + } + ); }); it('should not add debug param for non-APM endpoints', async () => { diff --git a/x-pack/plugins/apm/public/services/callApmApi.test.ts b/x-pack/plugins/apm/public/services/callApmApi.test.ts index 56146c49fc57d..b1d31586c2cee 100644 --- a/x-pack/plugins/apm/public/services/callApmApi.test.ts +++ b/x-pack/plugins/apm/public/services/callApmApi.test.ts @@ -24,7 +24,7 @@ describe('callApmApi', () => { it('should format the pathname with the given path params', async () => { await callApmApi({ - endpoint: 'GET /api/apm/{param1}/to/{param2}', + endpoint: 'GET /internal/apm/{param1}/to/{param2}', params: { path: { param1: 'foo', @@ -36,14 +36,14 @@ describe('callApmApi', () => { expect(callApi).toHaveBeenCalledWith( {}, expect.objectContaining({ - pathname: '/api/apm/foo/to/bar', + pathname: '/internal/apm/foo/to/bar', }) ); }); it('should add the query parameters to the options object', async () => { await callApmApi({ - endpoint: 'GET /api/apm', + endpoint: 'GET /internal/apm', params: { query: { foo: 'bar', @@ -55,7 +55,7 @@ describe('callApmApi', () => { expect(callApi).toHaveBeenCalledWith( {}, expect.objectContaining({ - pathname: '/api/apm', + pathname: '/internal/apm', query: { foo: 'bar', bar: 'foo', @@ -66,7 +66,7 @@ describe('callApmApi', () => { it('should stringify the body and add it to the options object', async () => { await callApmApi({ - endpoint: 'POST /api/apm', + endpoint: 'POST /internal/apm', params: { body: { foo: 'bar', @@ -78,7 +78,7 @@ describe('callApmApi', () => { expect(callApi).toHaveBeenCalledWith( {}, expect.objectContaining({ - pathname: '/api/apm', + pathname: '/internal/apm', method: 'post', body: { foo: 'bar', 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 1b95c88a5fdc5..f26fd85bde968 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 @@ -17,7 +17,7 @@ export const fetchObservabilityOverviewPageData = async ({ bucketSize, }: FetchDataParams): Promise => { const data = await callApmApi({ - endpoint: 'GET /api/apm/observability_overview', + endpoint: 'GET /internal/apm/observability_overview', signal: null, params: { query: { @@ -52,7 +52,7 @@ export const fetchObservabilityOverviewPageData = async ({ export async function getHasData() { return await callApmApi({ - endpoint: 'GET /api/apm/observability_overview/has_data', + endpoint: 'GET /internal/apm/observability_overview/has_data', signal: null, }); } diff --git a/x-pack/plugins/apm/public/services/rest/callApi.ts b/x-pack/plugins/apm/public/services/rest/callApi.ts index 53ddbed90413a..9b6d0c383e9a7 100644 --- a/x-pack/plugins/apm/public/services/rest/callApi.ts +++ b/x-pack/plugins/apm/public/services/rest/callApi.ts @@ -18,7 +18,7 @@ function fetchOptionsWithDebug( ) { const debugEnabled = inspectableEsQueriesEnabled && - startsWith(fetchOptions.pathname, '/api/apm'); + startsWith(fetchOptions.pathname, '/internal/apm'); const { body, ...rest } = fetchOptions; diff --git a/x-pack/plugins/apm/public/services/rest/index_pattern.ts b/x-pack/plugins/apm/public/services/rest/index_pattern.ts index 0bb5839759f64..f250c514000ba 100644 --- a/x-pack/plugins/apm/public/services/rest/index_pattern.ts +++ b/x-pack/plugins/apm/public/services/rest/index_pattern.ts @@ -9,7 +9,7 @@ import { callApmApi } from './createCallApmApi'; export const createStaticIndexPattern = async () => { return await callApmApi({ - endpoint: 'POST /api/apm/index_pattern/static', + endpoint: 'POST /internal/apm/index_pattern/static', signal: null, }); }; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx index 166839390bc22..7bebd89dc0021 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx @@ -12,7 +12,7 @@ import { POLICY_ELASTIC_AGENT_ON_CLOUD } from '../../../common/fleet'; import TutorialConfigAgent from './'; import { APIReturnType } from '../../services/rest/createCallApmApi'; -export type APIResponseType = APIReturnType<'GET /api/apm/fleet/agents'>; +export type APIResponseType = APIReturnType<'GET /internal/apm/fleet/agents'>; interface Args { apmAgent: string; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts b/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts index c6dc7265f3d3e..79eefeab85c63 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts +++ b/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts @@ -7,7 +7,7 @@ import { getPolicyOptions } from './get_policy_options'; import { APIReturnType } from '../../services/rest/createCallApmApi'; -type APIResponseType = APIReturnType<'GET /api/apm/fleet/agents'>; +type APIResponseType = APIReturnType<'GET /internal/apm/fleet/agents'>; const policyElasticAgentOnCloudAgent = { id: 'policy-elastic-agent-on-cloud', diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx index cfb51b4cd3b68..5ff1fd7f42119 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx @@ -21,7 +21,7 @@ import { CopyCommands } from './copy_commands'; import { getPolicyOptions, PolicyOption } from './get_policy_options'; import { PolicySelector } from './policy_selector'; -export type APIResponseType = APIReturnType<'GET /api/apm/fleet/agents'>; +export type APIResponseType = APIReturnType<'GET /internal/apm/fleet/agents'>; const CentralizedContainer = styled.div` display: flex; @@ -90,7 +90,7 @@ function TutorialConfigAgent({ async function fetchData() { setIsLoading(true); try { - const response = await http.get('/api/apm/fleet/agents'); + const response = await http.get('/internal/apm/fleet/agents'); if (response) { setData(response as APIResponseType); } diff --git a/x-pack/plugins/apm/public/tutorial/tutorial_apm_fleet_check.ts b/x-pack/plugins/apm/public/tutorial/tutorial_apm_fleet_check.ts index 8db8614d606a9..8502689724d04 100644 --- a/x-pack/plugins/apm/public/tutorial/tutorial_apm_fleet_check.ts +++ b/x-pack/plugins/apm/public/tutorial/tutorial_apm_fleet_check.ts @@ -9,7 +9,7 @@ import { callApmApi } from '../services/rest/createCallApmApi'; export async function hasFleetApmIntegrations() { try { const { hasData = false } = await callApmApi({ - endpoint: 'GET /api/apm/fleet/has_data', + endpoint: 'GET /internal/apm/fleet/has_data', signal: null, }); return hasData; diff --git a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx index 175d6797bb29f..f0d6dc72b72a5 100644 --- a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx +++ b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx @@ -29,7 +29,7 @@ const CentralizedContainer = styled.div` align-items: center; `; -type APIResponseType = APIReturnType<'GET /api/apm/fleet/has_data'>; +type APIResponseType = APIReturnType<'GET /internal/apm/fleet/has_data'>; function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { const [data, setData] = useState(); @@ -39,7 +39,7 @@ function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { async function fetchData() { setIsLoading(true); try { - const response = await http.get('/api/apm/fleet/has_data'); + const response = await http.get('/internal/apm/fleet/has_data'); setData(response as APIResponseType); } catch (e) { setIsLoading(false); diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index b7002ff7cbe79..22787b0301ce0 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -74,6 +74,8 @@ export type APMXPackConfig = TypeOf; export type APMConfig = ReturnType; // plugin config and ui indices settings +// All options should be documented in the APM configuration settings: https://github.com/elastic/kibana/blob/master/docs/settings/apm-settings.asciidoc +// and be included on cloud allow list unless there are specific reasons not to export function mergeConfigs( apmOssConfig: APMOSSConfig, apmConfig: APMXPackConfig 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 012cf304aa3ce..23a794bb7976a 100644 --- a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts @@ -34,7 +34,7 @@ const alertParamsRt = t.intersection([ export type AlertParams = t.TypeOf; const transactionErrorRateChartPreview = createApmServerRoute({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', + endpoint: 'GET /internal/apm/alerts/chart_preview/transaction_error_rate', params: t.type({ query: alertParamsRt }), options: { tags: ['access:apm'] }, handler: async (resources) => { @@ -52,7 +52,7 @@ const transactionErrorRateChartPreview = createApmServerRoute({ }); const transactionErrorCountChartPreview = createApmServerRoute({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', + endpoint: 'GET /internal/apm/alerts/chart_preview/transaction_error_count', params: t.type({ query: alertParamsRt }), options: { tags: ['access:apm'] }, handler: async (resources) => { @@ -71,7 +71,7 @@ const transactionErrorCountChartPreview = createApmServerRoute({ }); const transactionDurationChartPreview = createApmServerRoute({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', + endpoint: 'GET /internal/apm/alerts/chart_preview/transaction_duration', params: t.type({ query: alertParamsRt }), options: { tags: ['access:apm'] }, handler: async (resources) => { diff --git a/x-pack/plugins/apm/server/routes/backends.ts b/x-pack/plugins/apm/server/routes/backends.ts index 7aabfb3299134..feb4ca8bb978c 100644 --- a/x-pack/plugins/apm/server/routes/backends.ts +++ b/x-pack/plugins/apm/server/routes/backends.ts @@ -19,7 +19,7 @@ import { getThroughputChartsForBackend } from '../lib/backends/get_throughput_ch import { getErrorRateChartsForBackend } from '../lib/backends/get_error_rate_charts_for_backend'; const topBackendsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/backends/top_backends', + endpoint: 'GET /internal/apm/backends/top_backends', params: t.intersection([ t.type({ query: t.intersection([ @@ -65,7 +65,7 @@ const topBackendsRoute = createApmServerRoute({ }); const upstreamServicesForBackendRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/backends/{backendName}/upstream_services', + endpoint: 'GET /internal/apm/backends/{backendName}/upstream_services', params: t.intersection([ t.type({ path: t.type({ @@ -121,7 +121,7 @@ const upstreamServicesForBackendRoute = createApmServerRoute({ }); const backendMetadataRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/backends/{backendName}/metadata', + endpoint: 'GET /internal/apm/backends/{backendName}/metadata', params: t.type({ path: t.type({ backendName: t.string, @@ -150,7 +150,7 @@ const backendMetadataRoute = createApmServerRoute({ }); const backendLatencyChartsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/backends/{backendName}/charts/latency', + endpoint: 'GET /internal/apm/backends/{backendName}/charts/latency', params: t.type({ path: t.type({ backendName: t.string, @@ -193,7 +193,7 @@ const backendLatencyChartsRoute = createApmServerRoute({ }); const backendThroughputChartsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/backends/{backendName}/charts/throughput', + endpoint: 'GET /internal/apm/backends/{backendName}/charts/throughput', params: t.type({ path: t.type({ backendName: t.string, @@ -236,7 +236,7 @@ const backendThroughputChartsRoute = createApmServerRoute({ }); const backendFailedTransactionRateChartsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/backends/{backendName}/charts/error_rate', + endpoint: 'GET /internal/apm/backends/{backendName}/charts/error_rate', params: t.type({ path: t.type({ backendName: t.string, diff --git a/x-pack/plugins/apm/server/routes/environments.ts b/x-pack/plugins/apm/server/routes/environments.ts index 4b00320009e27..59e75f6f9c341 100644 --- a/x-pack/plugins/apm/server/routes/environments.ts +++ b/x-pack/plugins/apm/server/routes/environments.ts @@ -15,7 +15,7 @@ import { createApmServerRoute } from './create_apm_server_route'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; const environmentsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/environments', + endpoint: 'GET /internal/apm/environments', params: t.type({ query: t.intersection([ t.partial({ diff --git a/x-pack/plugins/apm/server/routes/errors.ts b/x-pack/plugins/apm/server/routes/errors.ts index 9a4199e319494..0864276b67fee 100644 --- a/x-pack/plugins/apm/server/routes/errors.ts +++ b/x-pack/plugins/apm/server/routes/errors.ts @@ -15,7 +15,7 @@ import { environmentRt, kueryRt, rangeRt } from './default_api_types'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; const errorsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/errors', + endpoint: 'GET /internal/apm/services/{serviceName}/errors', params: t.type({ path: t.type({ serviceName: t.string, @@ -54,7 +54,7 @@ const errorsRoute = createApmServerRoute({ }); const errorGroupsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/{groupId}', + endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}', params: t.type({ path: t.type({ serviceName: t.string, @@ -82,7 +82,7 @@ const errorGroupsRoute = createApmServerRoute({ }); const errorDistributionRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', + endpoint: 'GET /internal/apm/services/{serviceName}/errors/distribution', params: t.type({ path: t.type({ serviceName: t.string, diff --git a/x-pack/plugins/apm/server/routes/event_metadata.ts b/x-pack/plugins/apm/server/routes/event_metadata.ts index 8970ab8ffdeea..00241d2ef1c68 100644 --- a/x-pack/plugins/apm/server/routes/event_metadata.ts +++ b/x-pack/plugins/apm/server/routes/event_metadata.ts @@ -13,7 +13,7 @@ import { processorEventRt } from '../../common/processor_event'; import { setupRequest } from '../lib/helpers/setup_request'; const eventMetadataRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/event_metadata/{processorEvent}/{id}', + endpoint: 'GET /internal/apm/event_metadata/{processorEvent}/{id}', options: { tags: ['access:apm'] }, params: t.type({ path: t.type({ diff --git a/x-pack/plugins/apm/server/routes/fallback_to_transactions.ts b/x-pack/plugins/apm/server/routes/fallback_to_transactions.ts index 8f30b9c089821..ba74cc0b7a88a 100644 --- a/x-pack/plugins/apm/server/routes/fallback_to_transactions.ts +++ b/x-pack/plugins/apm/server/routes/fallback_to_transactions.ts @@ -13,7 +13,7 @@ import { createApmServerRouteRepository } from './create_apm_server_route_reposi import { kueryRt, rangeRt } from './default_api_types'; const fallbackToTransactionsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/fallback_to_transactions', + endpoint: 'GET /internal/apm/fallback_to_transactions', params: t.partial({ query: t.intersection([kueryRt, t.partial(rangeRt.props)]), }), diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts index 5bfbfef5e6800..d8097228df0dc 100644 --- a/x-pack/plugins/apm/server/routes/fleet.ts +++ b/x-pack/plugins/apm/server/routes/fleet.ts @@ -28,7 +28,7 @@ import { createApmServerRoute } from './create_apm_server_route'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; const hasFleetDataRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/fleet/has_data', + endpoint: 'GET /internal/apm/fleet/has_data', options: { tags: [] }, handler: async ({ core, plugins }) => { const fleetPluginStart = await plugins.fleet?.start(); @@ -44,7 +44,7 @@ const hasFleetDataRoute = createApmServerRoute({ }); const fleetAgentsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/fleet/agents', + endpoint: 'GET /internal/apm/fleet/agents', options: { tags: [] }, handler: async ({ core, plugins }) => { const cloudSetup = plugins.cloud?.setup; @@ -92,7 +92,7 @@ const fleetAgentsRoute = createApmServerRoute({ }); const saveApmServerSchemaRoute = createApmServerRoute({ - endpoint: 'POST /api/apm/fleet/apm_server_schema', + endpoint: 'POST /internal/apm/fleet/apm_server_schema', options: { tags: ['access:apm', 'access:apm_write'] }, params: t.type({ body: t.type({ @@ -113,7 +113,7 @@ const saveApmServerSchemaRoute = createApmServerRoute({ }); const getUnsupportedApmServerSchemaRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/fleet/apm_server_schema/unsupported', + endpoint: 'GET /internal/apm/fleet/apm_server_schema/unsupported', options: { tags: ['access:apm'] }, handler: async (resources) => { const { context } = resources; @@ -125,7 +125,7 @@ const getUnsupportedApmServerSchemaRoute = createApmServerRoute({ }); const getMigrationCheckRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/fleet/migration_check', + endpoint: 'GET /internal/apm/fleet/migration_check', options: { tags: ['access:apm'] }, handler: async (resources) => { const { plugins, context, config, request } = resources; @@ -154,7 +154,7 @@ const getMigrationCheckRoute = createApmServerRoute({ }); const createCloudApmPackagePolicyRoute = createApmServerRoute({ - endpoint: 'POST /api/apm/fleet/cloud_apm_package_policy', + endpoint: 'POST /internal/apm/fleet/cloud_apm_package_policy', options: { tags: ['access:apm', 'access:apm_write'] }, handler: async (resources) => { const { plugins, context, config, request, logger } = resources; diff --git a/x-pack/plugins/apm/server/routes/historical_data/index.ts b/x-pack/plugins/apm/server/routes/historical_data/index.ts index be3fd29d14b9d..fb67dc4f5b649 100644 --- a/x-pack/plugins/apm/server/routes/historical_data/index.ts +++ b/x-pack/plugins/apm/server/routes/historical_data/index.ts @@ -11,7 +11,7 @@ import { createApmServerRouteRepository } from '../create_apm_server_route_repos import { hasHistoricalAgentData } from './has_historical_agent_data'; const hasDataRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/has_data', + endpoint: 'GET /internal/apm/has_data', options: { tags: ['access:apm'] }, handler: async (resources) => { const setup = await setupRequest(resources); diff --git a/x-pack/plugins/apm/server/routes/index_pattern.ts b/x-pack/plugins/apm/server/routes/index_pattern.ts index c957e828bf12a..a996636ae56a1 100644 --- a/x-pack/plugins/apm/server/routes/index_pattern.ts +++ b/x-pack/plugins/apm/server/routes/index_pattern.ts @@ -12,7 +12,7 @@ import { getDynamicIndexPattern } from '../lib/index_pattern/get_dynamic_index_p import { createApmServerRoute } from './create_apm_server_route'; const staticIndexPatternRoute = createApmServerRoute({ - endpoint: 'POST /api/apm/index_pattern/static', + endpoint: 'POST /internal/apm/index_pattern/static', options: { tags: ['access:apm'] }, handler: async (resources) => { const { @@ -43,7 +43,7 @@ const staticIndexPatternRoute = createApmServerRoute({ }); const dynamicIndexPatternRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/index_pattern/dynamic', + endpoint: 'GET /internal/apm/index_pattern/dynamic', options: { tags: ['access:apm'] }, handler: async ({ context, config, logger }) => { const dynamicIndexPattern = await getDynamicIndexPattern({ diff --git a/x-pack/plugins/apm/server/routes/metrics.ts b/x-pack/plugins/apm/server/routes/metrics.ts index ea4878016652f..8b6b16a26f1d8 100644 --- a/x-pack/plugins/apm/server/routes/metrics.ts +++ b/x-pack/plugins/apm/server/routes/metrics.ts @@ -13,7 +13,7 @@ import { createApmServerRouteRepository } from './create_apm_server_route_reposi import { environmentRt, kueryRt, rangeRt } from './default_api_types'; const metricsChartsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/metrics/charts', + endpoint: 'GET /internal/apm/services/{serviceName}/metrics/charts', params: t.type({ path: t.type({ serviceName: t.string, diff --git a/x-pack/plugins/apm/server/routes/observability_overview.ts b/x-pack/plugins/apm/server/routes/observability_overview.ts index feaa6b580dac7..a99291ff32bb6 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview.ts @@ -17,7 +17,7 @@ import { createApmServerRouteRepository } from './create_apm_server_route_reposi import { createApmServerRoute } from './create_apm_server_route'; const observabilityOverviewHasDataRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/observability_overview/has_data', + endpoint: 'GET /internal/apm/observability_overview/has_data', options: { tags: ['access:apm'] }, handler: async (resources) => { const setup = await setupRequest(resources); @@ -26,7 +26,7 @@ const observabilityOverviewHasDataRoute = createApmServerRoute({ }); const observabilityOverviewRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/observability_overview', + endpoint: 'GET /internal/apm/observability_overview', params: t.type({ query: t.intersection([rangeRt, t.type({ bucketSize: t.string })]), }), diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts index 8fb3abe99e36c..f9062ac13e049 100644 --- a/x-pack/plugins/apm/server/routes/service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map.ts @@ -20,7 +20,7 @@ import { createApmServerRouteRepository } from './create_apm_server_route_reposi import { environmentRt, rangeRt } from './default_api_types'; const serviceMapRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/service-map', + endpoint: 'GET /internal/apm/service-map', params: t.type({ query: t.intersection([ t.partial({ @@ -70,7 +70,7 @@ const serviceMapRoute = createApmServerRoute({ }); const serviceMapServiceNodeRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/service-map/service/{serviceName}', + endpoint: 'GET /internal/apm/service-map/service/{serviceName}', params: t.type({ path: t.type({ serviceName: t.string, @@ -114,7 +114,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ }); const serviceMapBackendNodeRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/service-map/backend/{backendName}', + endpoint: 'GET /internal/apm/service-map/backend/{backendName}', params: t.type({ path: t.type({ backendName: t.string, diff --git a/x-pack/plugins/apm/server/routes/service_nodes.ts b/x-pack/plugins/apm/server/routes/service_nodes.ts index 4bd1c93599723..2081b794f8ab1 100644 --- a/x-pack/plugins/apm/server/routes/service_nodes.ts +++ b/x-pack/plugins/apm/server/routes/service_nodes.ts @@ -14,7 +14,7 @@ import { rangeRt, kueryRt } from './default_api_types'; import { environmentRt } from '../../common/environment_rt'; const serviceNodesRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/serviceNodes', + endpoint: 'GET /internal/apm/services/{serviceName}/serviceNodes', params: t.type({ path: t.type({ serviceName: t.string, diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index a612575d16ff6..d4af7315b9c23 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -48,7 +48,7 @@ import { getServiceDependenciesBreakdown } from '../lib/services/get_service_dep import { getBucketSizeForAggregatedTransactions } from '../lib/helpers/get_bucket_size_for_aggregated_transactions'; const servicesRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services', + endpoint: 'GET /internal/apm/services', params: t.type({ query: t.intersection([environmentRt, kueryRt, rangeRt]), }), @@ -75,7 +75,7 @@ const servicesRoute = createApmServerRoute({ }); const servicesDetailedStatisticsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/detailed_statistics', + endpoint: 'GET /internal/apm/services/detailed_statistics', params: t.type({ query: t.intersection([ environmentRt, @@ -116,7 +116,7 @@ const servicesDetailedStatisticsRoute = createApmServerRoute({ }); const serviceMetadataDetailsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/metadata/details', + endpoint: 'GET /internal/apm/services/{serviceName}/metadata/details', params: t.type({ path: t.type({ serviceName: t.string }), query: rangeRt, @@ -147,7 +147,7 @@ const serviceMetadataDetailsRoute = createApmServerRoute({ }); const serviceMetadataIconsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/metadata/icons', + endpoint: 'GET /internal/apm/services/{serviceName}/metadata/icons', params: t.type({ path: t.type({ serviceName: t.string }), query: rangeRt, @@ -178,7 +178,7 @@ const serviceMetadataIconsRoute = createApmServerRoute({ }); const serviceAgentRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/agent', + endpoint: 'GET /internal/apm/services/{serviceName}/agent', params: t.type({ path: t.type({ serviceName: t.string, @@ -211,7 +211,7 @@ const serviceAgentRoute = createApmServerRoute({ }); const serviceTransactionTypesRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/transaction_types', + endpoint: 'GET /internal/apm/services/{serviceName}/transaction_types', params: t.type({ path: t.type({ serviceName: t.string, @@ -243,7 +243,7 @@ const serviceTransactionTypesRoute = createApmServerRoute({ const serviceNodeMetadataRoute = createApmServerRoute({ endpoint: - 'GET /api/apm/services/{serviceName}/node/{serviceNodeName}/metadata', + 'GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata', params: t.type({ path: t.type({ serviceName: t.string, @@ -383,7 +383,8 @@ const serviceAnnotationsCreateRoute = createApmServerRoute({ }); const serviceErrorGroupsMainStatisticsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/error_groups/main_statistics', + endpoint: + 'GET /internal/apm/services/{serviceName}/error_groups/main_statistics', params: t.type({ path: t.type({ serviceName: t.string, @@ -420,7 +421,7 @@ const serviceErrorGroupsMainStatisticsRoute = createApmServerRoute({ const serviceErrorGroupsDetailedStatisticsRoute = createApmServerRoute({ endpoint: - 'GET /api/apm/services/{serviceName}/error_groups/detailed_statistics', + 'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics', params: t.type({ path: t.type({ serviceName: t.string, @@ -474,7 +475,7 @@ const serviceErrorGroupsDetailedStatisticsRoute = createApmServerRoute({ }); const serviceThroughputRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/throughput', + endpoint: 'GET /internal/apm/services/{serviceName}/throughput', params: t.type({ path: t.type({ serviceName: t.string, @@ -556,7 +557,7 @@ const serviceThroughputRoute = createApmServerRoute({ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ endpoint: - 'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics', + 'GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics', params: t.type({ path: t.type({ serviceName: t.string, @@ -630,7 +631,7 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ const serviceInstancesDetailedStatisticsRoute = createApmServerRoute({ endpoint: - 'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics', + 'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics', params: t.type({ path: t.type({ serviceName: t.string, @@ -693,7 +694,7 @@ const serviceInstancesDetailedStatisticsRoute = createApmServerRoute({ export const serviceInstancesMetadataDetails = createApmServerRoute({ endpoint: - 'GET /api/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}', + 'GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}', params: t.type({ path: t.type({ serviceName: t.string, @@ -718,7 +719,7 @@ export const serviceInstancesMetadataDetails = createApmServerRoute({ }); export const serviceDependenciesRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/dependencies', + endpoint: 'GET /internal/apm/services/{serviceName}/dependencies', params: t.type({ path: t.type({ serviceName: t.string, @@ -773,7 +774,7 @@ export const serviceDependenciesRoute = createApmServerRoute({ }); export const serviceDependenciesBreakdownRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/dependencies/breakdown', + endpoint: 'GET /internal/apm/services/{serviceName}/dependencies/breakdown', params: t.type({ path: t.type({ serviceName: t.string, @@ -805,7 +806,7 @@ export const serviceDependenciesBreakdownRoute = createApmServerRoute({ }); const serviceProfilingTimelineRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/profiling/timeline', + endpoint: 'GET /internal/apm/services/{serviceName}/profiling/timeline', params: t.type({ path: t.type({ serviceName: t.string, @@ -837,7 +838,7 @@ const serviceProfilingTimelineRoute = createApmServerRoute({ }); const serviceProfilingStatisticsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/profiling/statistics', + endpoint: 'GET /internal/apm/services/{serviceName}/profiling/statistics', params: t.type({ path: t.type({ serviceName: t.string, @@ -886,7 +887,7 @@ const serviceProfilingStatisticsRoute = createApmServerRoute({ }); const serviceAlertsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/alerts', + endpoint: 'GET /internal/apm/services/{serviceName}/alerts', params: t.type({ path: t.type({ serviceName: t.string, @@ -922,7 +923,7 @@ const serviceAlertsRoute = createApmServerRoute({ }); const serviceInfrastructureRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/infrastructure', + endpoint: 'GET /internal/apm/services/{serviceName}/infrastructure', params: t.type({ path: t.type({ serviceName: t.string, diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts index c7e45eb8c32e5..78db4e0c14b36 100644 --- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts +++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts @@ -23,7 +23,7 @@ import { createApmServerRouteRepository } from '../create_apm_server_route_repos // get ML anomaly detection jobs for each environment const anomalyDetectionJobsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/settings/anomaly-detection/jobs', + endpoint: 'GET /internal/apm/settings/anomaly-detection/jobs', options: { tags: ['access:apm', 'access:ml:canGetJobs'], }, @@ -51,7 +51,7 @@ const anomalyDetectionJobsRoute = createApmServerRoute({ // create new ML anomaly detection jobs for each given environment const createAnomalyDetectionJobsRoute = createApmServerRoute({ - endpoint: 'POST /api/apm/settings/anomaly-detection/jobs', + endpoint: 'POST /internal/apm/settings/anomaly-detection/jobs', options: { tags: ['access:apm', 'access:apm_write', 'access:ml:canCreateJob'], }, @@ -83,7 +83,7 @@ const createAnomalyDetectionJobsRoute = createApmServerRoute({ // get all available environments to create anomaly detection jobs for const anomalyDetectionEnvironmentsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/settings/anomaly-detection/environments', + endpoint: 'GET /internal/apm/settings/anomaly-detection/environments', options: { tags: ['access:apm'] }, handler: async (resources) => { const setup = await setupRequest(resources); diff --git a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts index 003471aa89f39..1cba5f972c27e 100644 --- a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts +++ b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts @@ -16,7 +16,7 @@ import { saveApmIndices } from '../../lib/settings/apm_indices/save_apm_indices' // get list of apm indices and values const apmIndexSettingsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/settings/apm-index-settings', + endpoint: 'GET /internal/apm/settings/apm-index-settings', options: { tags: ['access:apm'] }, handler: async ({ config, context }) => { const apmIndexSettings = await getApmIndexSettings({ config, context }); @@ -26,7 +26,7 @@ const apmIndexSettingsRoute = createApmServerRoute({ // get apm indices configuration object const apmIndicesRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/settings/apm-indices', + endpoint: 'GET /internal/apm/settings/apm-indices', options: { tags: ['access:apm'] }, handler: async (resources) => { const { context, config } = resources; @@ -39,7 +39,7 @@ const apmIndicesRoute = createApmServerRoute({ // save ui indices const saveApmIndicesRoute = createApmServerRoute({ - endpoint: 'POST /api/apm/settings/apm-indices/save', + endpoint: 'POST /internal/apm/settings/apm-indices/save', options: { tags: ['access:apm', 'access:apm_write'], }, diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts index c9c5d236c14f9..af880898176bb 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts @@ -25,7 +25,7 @@ import { createApmServerRoute } from '../create_apm_server_route'; import { createApmServerRouteRepository } from '../create_apm_server_route_repository'; const customLinkTransactionRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/settings/custom_links/transaction', + endpoint: 'GET /internal/apm/settings/custom_links/transaction', options: { tags: ['access:apm'] }, params: t.partial({ query: filterOptionsRt, @@ -41,7 +41,7 @@ const customLinkTransactionRoute = createApmServerRoute({ }); const listCustomLinksRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/settings/custom_links', + endpoint: 'GET /internal/apm/settings/custom_links', options: { tags: ['access:apm'] }, params: t.partial({ query: filterOptionsRt, @@ -63,7 +63,7 @@ const listCustomLinksRoute = createApmServerRoute({ }); const createCustomLinkRoute = createApmServerRoute({ - endpoint: 'POST /api/apm/settings/custom_links', + endpoint: 'POST /internal/apm/settings/custom_links', params: t.type({ body: payloadRt, }), @@ -86,7 +86,7 @@ const createCustomLinkRoute = createApmServerRoute({ }); const updateCustomLinkRoute = createApmServerRoute({ - endpoint: 'PUT /api/apm/settings/custom_links/{id}', + endpoint: 'PUT /internal/apm/settings/custom_links/{id}', params: t.type({ path: t.type({ id: t.string, @@ -116,7 +116,7 @@ const updateCustomLinkRoute = createApmServerRoute({ }); const deleteCustomLinkRoute = createApmServerRoute({ - endpoint: 'DELETE /api/apm/settings/custom_links/{id}', + endpoint: 'DELETE /internal/apm/settings/custom_links/{id}', params: t.type({ path: t.type({ id: t.string, diff --git a/x-pack/plugins/apm/server/routes/traces.ts b/x-pack/plugins/apm/server/routes/traces.ts index 52aa507bb38b1..a71b7eefeed3f 100644 --- a/x-pack/plugins/apm/server/routes/traces.ts +++ b/x-pack/plugins/apm/server/routes/traces.ts @@ -17,7 +17,7 @@ import { createApmServerRouteRepository } from './create_apm_server_route_reposi import { getTransaction } from '../lib/transactions/get_transaction'; const tracesRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/traces', + endpoint: 'GET /internal/apm/traces', params: t.type({ query: t.intersection([environmentRt, kueryRt, rangeRt]), }), @@ -41,7 +41,7 @@ const tracesRoute = createApmServerRoute({ }); const tracesByIdRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/traces/{traceId}', + endpoint: 'GET /internal/apm/traces/{traceId}', params: t.type({ path: t.type({ traceId: t.string, @@ -60,7 +60,7 @@ const tracesByIdRoute = createApmServerRoute({ }); const rootTransactionByTraceIdRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/traces/{traceId}/root_transaction', + endpoint: 'GET /internal/apm/traces/{traceId}/root_transaction', params: t.type({ path: t.type({ traceId: t.string, @@ -76,7 +76,7 @@ const rootTransactionByTraceIdRoute = createApmServerRoute({ }); const transactionByIdRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/transactions/{transactionId}', + endpoint: 'GET /internal/apm/transactions/{transactionId}', params: t.type({ path: t.type({ transactionId: t.string, diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index a8d5c11699093..0e24d64d8c6c7 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -31,7 +31,7 @@ import { const transactionGroupsMainStatisticsRoute = createApmServerRoute({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics', + 'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics', params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ @@ -85,7 +85,7 @@ const transactionGroupsMainStatisticsRoute = createApmServerRoute({ const transactionGroupsDetailedStatisticsRoute = createApmServerRoute({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/detailed_statistics', + 'GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics', params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ @@ -150,7 +150,8 @@ const transactionGroupsDetailedStatisticsRoute = createApmServerRoute({ }); const transactionLatencyChartsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/transactions/charts/latency', + endpoint: + 'GET /internal/apm/services/{serviceName}/transactions/charts/latency', params: t.type({ path: t.type({ serviceName: t.string, @@ -227,7 +228,8 @@ const transactionLatencyChartsRoute = createApmServerRoute({ }); const transactionTraceSamplesRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/transactions/traces/samples', + endpoint: + 'GET /internal/apm/services/{serviceName}/transactions/traces/samples', params: t.type({ path: t.type({ serviceName: t.string, @@ -284,7 +286,8 @@ const transactionTraceSamplesRoute = createApmServerRoute({ }); const transactionChartsBreakdownRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/transaction/charts/breakdown', + endpoint: + 'GET /internal/apm/services/{serviceName}/transaction/charts/breakdown', params: t.type({ path: t.type({ serviceName: t.string, @@ -321,7 +324,7 @@ const transactionChartsBreakdownRoute = createApmServerRoute({ const transactionChartsErrorRateRoute = createApmServerRoute({ endpoint: - 'GET /api/apm/services/{serviceName}/transactions/charts/error_rate', + 'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate', params: t.type({ path: t.type({ serviceName: t.string, diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js index b9e47b029e302..bcd4aaf82eeb3 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js @@ -314,7 +314,8 @@ describe('', () => { }); }); - describe('detail panel', () => { + // FLAKY: https://github.com/elastic/kibana/issues/100951 + describe.skip('detail panel', () => { test('should open a detail panel when clicking on a follower index', async () => { expect(exists('followerIndexDetail')).toBe(false); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/crawler_status_indicator.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/crawler_status_indicator.test.tsx index 9d585789d8e50..c46c360934d0b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/crawler_status_indicator.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/crawler_status_indicator.test.tsx @@ -37,7 +37,7 @@ describe('CrawlerStatusIndicator', () => { describe('when status is not a valid status', () => { it('is disabled', () => { // this tests a codepath that should be impossible to reach, status should always be a CrawlerStatus - // but we use a switch statement and need to test the default case for this to recieve 100% coverage + // but we use a switch statement and need to test the default case for this to receive 100% coverage setMockValues({ ...MOCK_VALUES, mostRecentCrawlRequestStatus: null, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx index bf64101527fd2..4248eb62e33f1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx @@ -45,6 +45,7 @@ const MOCK_RESPONSE: SuggestionsAPIResponse = { updated_at: '2021-07-08T14:35:50Z', promoted: ['1', '2'], status: 'applied', + operation: 'create', }, ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts index 6b087c1dcacd6..f00da5deec7e3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts @@ -14,6 +14,7 @@ export interface CurationSuggestion { promoted: string[]; status: 'pending' | 'applied' | 'automated' | 'rejected' | 'disabled'; curation_id?: string; + operation: 'create' | 'update' | 'delete'; override_curation_id?: string; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.test.tsx index 4bd586d9d2e91..62cbc479f1d8e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.test.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import { setMockActions } from '../../../../../__mocks__/kea_logic'; + import React from 'react'; import { shallow } from 'enzyme'; @@ -12,18 +14,22 @@ import { shallow } from 'enzyme'; import { CurationActionBar } from './curation_action_bar'; describe('CurationActionBar', () => { - const handleAcceptClick = jest.fn(); - const handleRejectClick = jest.fn(); + const actions = { + acceptSuggestion: jest.fn(), + rejectSuggestion: jest.fn(), + }; + + beforeAll(() => { + setMockActions(actions); + }); it('renders', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); wrapper.find('[data-test-subj="rejectButton"]').simulate('click'); - expect(handleRejectClick).toHaveBeenCalled(); + expect(actions.rejectSuggestion).toHaveBeenCalled(); wrapper.find('[data-test-subj="acceptButton"]').simulate('click'); - expect(handleAcceptClick).toHaveBeenCalled(); + expect(actions.acceptSuggestion).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.tsx index 42f4cbbb7d7a9..536d30637549e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.tsx @@ -7,17 +7,17 @@ import React from 'react'; +import { useActions } from 'kea'; + import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { CurationActionsPopover } from './curation_actions_popover'; +import { CurationSuggestionLogic } from './curation_suggestion_logic'; -interface Props { - onAcceptClick: (event: React.MouseEvent) => void; - onRejectClick: (event: React.MouseEvent) => void; -} +export const CurationActionBar: React.FC = () => { + const { acceptSuggestion, rejectSuggestion } = useActions(CurationSuggestionLogic); -export const CurationActionBar: React.FC = ({ onAcceptClick, onRejectClick }) => { return ( @@ -41,7 +41,7 @@ export const CurationActionBar: React.FC = ({ onAcceptClick, onRejectClic color="danger" iconType="crossInACircleFilled" data-test-subj="rejectButton" - onClick={onRejectClick} + onClick={rejectSuggestion} > {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.rejectButtonLabel', @@ -55,7 +55,7 @@ export const CurationActionBar: React.FC = ({ onAcceptClick, onRejectClic color="success" iconType="checkInCircleFilled" data-test-subj="acceptButton" - onClick={onAcceptClick} + onClick={acceptSuggestion} > {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.acceptButtonLabel', @@ -64,12 +64,7 @@ export const CurationActionBar: React.FC = ({ onAcceptClick, onRejectClic - {}} - onAutomate={() => {}} - onReject={() => {}} - onTurnOff={() => {}} - /> + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.test.tsx index 33d00ca2b7899..c5caff68bc0fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.test.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import { setMockActions } from '../../../../../__mocks__/kea_logic'; + import React from 'react'; import { shallow } from 'enzyme'; @@ -12,48 +14,40 @@ import { shallow } from 'enzyme'; import { CurationActionsPopover } from './curation_actions_popover'; describe('CurationActionsPopover', () => { - const handleAccept = jest.fn(); - const handleAutomate = jest.fn(); - const handleReject = jest.fn(); - const handleTurnOff = jest.fn(); + const actions = { + acceptSuggestion: jest.fn(), + acceptAndAutomateSuggestion: jest.fn(), + rejectSuggestion: jest.fn(), + rejectAndDisableSuggestion: jest.fn(), + }; + + beforeAll(() => { + setMockActions(actions); + }); beforeEach(() => { jest.clearAllMocks(); }); it('renders', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper.isEmptyRender()).toBe(false); wrapper.find('[data-test-subj="acceptButton"]').simulate('click'); - expect(handleAccept).toHaveBeenCalled(); + expect(actions.acceptSuggestion).toHaveBeenCalled(); wrapper.find('[data-test-subj="automateButton"]').simulate('click'); - expect(handleAutomate).toHaveBeenCalled(); + expect(actions.acceptAndAutomateSuggestion).toHaveBeenCalled(); wrapper.find('[data-test-subj="rejectButton"]').simulate('click'); - expect(handleReject).toHaveBeenCalled(); + expect(actions.rejectSuggestion).toHaveBeenCalled(); wrapper.find('[data-test-subj="turnoffButton"]').simulate('click'); - expect(handleTurnOff).toHaveBeenCalled(); + expect(actions.rejectAndDisableSuggestion).toHaveBeenCalled(); }); it('can open and close', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper.prop('isOpen')).toBe(false); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.tsx index ef7b42fb705f1..0ae923830cd78 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.tsx @@ -7,6 +7,8 @@ import React, { useState } from 'react'; +import { useActions } from 'kea'; + import { EuiButtonIcon, EuiListGroup, @@ -16,20 +18,16 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -interface Props { - onAccept: () => void; - onAutomate: () => void; - onReject: () => void; - onTurnOff: () => void; -} +import { CurationSuggestionLogic } from './curation_suggestion_logic'; -export const CurationActionsPopover: React.FC = ({ - onAccept, - onAutomate, - onReject, - onTurnOff, -}) => { +export const CurationActionsPopover: React.FC = () => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const { + acceptSuggestion, + acceptAndAutomateSuggestion, + rejectSuggestion, + rejectAndDisableSuggestion, + } = useActions(CurationSuggestionLogic); const onButtonClick = () => setIsPopoverOpen(!isPopoverOpen); const closePopover = () => setIsPopoverOpen(false); @@ -63,7 +61,7 @@ export const CurationActionsPopover: React.FC = ({ 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsAcceptButtonLabel', { defaultMessage: 'Accept this suggestion' } )} - onClick={onAccept} + onClick={acceptSuggestion} data-test-subj="acceptButton" /> = ({ 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsAutomateButtonLabel', { defaultMessage: 'Automate - always accept new suggestions for this query' } )} - onClick={onAutomate} + onClick={acceptAndAutomateSuggestion} data-test-subj="automateButton" /> = ({ 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsRejectButtonLabel', { defaultMessage: 'Reject this suggestion' } )} - onClick={onReject} + onClick={rejectSuggestion} data-test-subj="rejectButton" /> = ({ 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsTurnOffButtonLabel', { defaultMessage: 'Reject and turn off suggestions for this query' } )} - onClick={onTurnOff} + onClick={rejectAndDisableSuggestion} data-test-subj="turnoffButton" /> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx index 07184f6d668e2..8e1e6487197f9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx @@ -65,10 +65,7 @@ export const CurationSuggestion: React.FC = () => { pageTitle: suggestionQuery, }} > - alert('Accepted')} - onRejectClick={() => alert('Rejected')} - /> + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts index af694c3756fd1..2ace55133d6fd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts @@ -9,6 +9,7 @@ import { LogicMounter, mockFlashMessageHelpers, mockHttpValues, + mockKibanaValues, } from '../../../../../__mocks__/kea_logic'; import { set } from 'lodash/fp'; @@ -32,7 +33,8 @@ const suggestion: CurationSuggestion = { query: 'foo', updated_at: '2021-07-08T14:35:50Z', promoted: ['1', '2', '3'], - status: 'applied', + status: 'pending', + operation: 'create', }; const curation = { @@ -115,13 +117,51 @@ const MOCK_DOCUMENTS_RESPONSE = { describe('CurationSuggestionLogic', () => { const { mount } = new LogicMounter(CurationSuggestionLogic); - const { flashAPIErrors } = mockFlashMessageHelpers; + const { flashAPIErrors, setQueuedErrorMessage } = mockFlashMessageHelpers; + const { navigateToUrl } = mockKibanaValues; + const mountLogic = (props: object = {}) => { mount(props, { query: 'foo-query' }); }; const { http } = mockHttpValues; + const itHandlesInlineErrors = (callback: () => void) => { + it('handles inline errors', async () => { + http.put.mockReturnValueOnce( + Promise.resolve({ + results: [ + { + error: 'error', + }, + ], + }) + ); + mountLogic({ + suggestion, + }); + + callback(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }; + + const itHandlesErrors = (httpMethod: any, callback: () => void) => { + it('handles errors', async () => { + httpMethod.mockReturnValueOnce(Promise.reject('error')); + mountLogic({ + suggestion, + }); + + callback(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }; + beforeEach(() => { jest.clearAllMocks(); }); @@ -207,7 +247,8 @@ describe('CurationSuggestionLogic', () => { query: 'foo', updated_at: '2021-07-08T14:35:50Z', promoted: ['1', '2', '3'], - status: 'applied', + status: 'pending', + operation: 'create', }, // Note that these were re-ordered to match the 'promoted' list above, and since document // 3 was not found it is not included in this list @@ -243,9 +284,7 @@ describe('CurationSuggestionLogic', () => { ); http.post.mockReturnValueOnce(Promise.resolve(MOCK_DOCUMENTS_RESPONSE)); http.get.mockReturnValueOnce(Promise.resolve(curation)); - mountLogic({ - suggestion: set('curation_id', 'cur-6155e69c7a2f2e4f756303fd', suggestion), - }); + mountLogic(); jest.spyOn(CurationSuggestionLogic.actions, 'onSuggestionLoaded'); CurationSuggestionLogic.actions.loadSuggestion(); @@ -255,7 +294,6 @@ describe('CurationSuggestionLogic', () => { '/internal/app_search/engines/some-engine/curations/cur-6155e69c7a2f2e4f756303fd', { query: { skip_record_analytics: 'true' } } ); - await nextTick(); expect(CurationSuggestionLogic.actions.onSuggestionLoaded).toHaveBeenCalledWith({ suggestion: expect.any(Object), @@ -264,14 +302,204 @@ describe('CurationSuggestionLogic', () => { }); }); - it('handles errors', async () => { - http.post.mockReturnValueOnce(Promise.reject('error')); - mount(); + // This could happen if a user applies a suggestion and then navigates back to a detail page via + // the back button, etc. The suggestion still exists, it's just not in a "pending" state + // so we can show it.ga + it('will redirect if the suggestion is not found', async () => { + http.post.mockReturnValueOnce(Promise.resolve(set('results', [], MOCK_RESPONSE))); + mountLogic(); + CurationSuggestionLogic.actions.loadSuggestion(); + await nextTick(); + expect(setQueuedErrorMessage).toHaveBeenCalled(); + expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); + }); + itHandlesErrors(http.post, () => { CurationSuggestionLogic.actions.loadSuggestion(); + }); + }); + + describe('acceptSuggestion', () => { + it('will make an http call to apply the suggestion, and then navigate to that detail page', async () => { + http.put.mockReturnValueOnce( + Promise.resolve({ + results: [ + { + ...suggestion, + status: 'accepted', + curation_id: 'cur-6155e69c7a2f2e4f756303fd', + }, + ], + }) + ); + mountLogic({ + suggestion, + }); + + CurationSuggestionLogic.actions.acceptSuggestion(); + await nextTick(); + + expect(http.put).toHaveBeenCalledWith( + '/internal/app_search/engines/some-engine/search_relevance_suggestions', + { + body: JSON.stringify([ + { + query: 'foo', + type: 'curation', + status: 'applied', + }, + ]), + } + ); + + expect(navigateToUrl).toHaveBeenCalledWith( + '/engines/some-engine/curations/cur-6155e69c7a2f2e4f756303fd' + ); + }); + + itHandlesErrors(http.put, () => { + CurationSuggestionLogic.actions.acceptSuggestion(); + }); + + itHandlesInlineErrors(() => { + CurationSuggestionLogic.actions.acceptSuggestion(); + }); + }); + + describe('acceptAndAutomateSuggestion', () => { + it('will make an http call to apply the suggestion, and then navigate to that detail page', async () => { + http.put.mockReturnValueOnce( + Promise.resolve({ + results: [ + { + ...suggestion, + status: 'accepted', + curation_id: 'cur-6155e69c7a2f2e4f756303fd', + }, + ], + }) + ); + mountLogic({ + suggestion, + }); + + CurationSuggestionLogic.actions.acceptAndAutomateSuggestion(); + await nextTick(); + + expect(http.put).toHaveBeenCalledWith( + '/internal/app_search/engines/some-engine/search_relevance_suggestions', + { + body: JSON.stringify([ + { + query: 'foo', + type: 'curation', + status: 'automated', + }, + ]), + } + ); + + expect(navigateToUrl).toHaveBeenCalledWith( + '/engines/some-engine/curations/cur-6155e69c7a2f2e4f756303fd' + ); + }); + + itHandlesErrors(http.put, () => { + CurationSuggestionLogic.actions.acceptAndAutomateSuggestion(); + }); + + itHandlesInlineErrors(() => { + CurationSuggestionLogic.actions.acceptAndAutomateSuggestion(); + }); + }); + + describe('rejectSuggestion', () => { + it('will make an http call to apply the suggestion, and then navigate back the curations page', async () => { + http.put.mockReturnValueOnce( + Promise.resolve({ + results: [ + { + ...suggestion, + status: 'rejected', + curation_id: 'cur-6155e69c7a2f2e4f756303fd', + }, + ], + }) + ); + mountLogic({ + suggestion, + }); + + CurationSuggestionLogic.actions.rejectSuggestion(); + await nextTick(); + + expect(http.put).toHaveBeenCalledWith( + '/internal/app_search/engines/some-engine/search_relevance_suggestions', + { + body: JSON.stringify([ + { + query: 'foo', + type: 'curation', + status: 'rejected', + }, + ]), + } + ); + + expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); + }); + + itHandlesErrors(http.put, () => { + CurationSuggestionLogic.actions.rejectSuggestion(); + }); + + itHandlesInlineErrors(() => { + CurationSuggestionLogic.actions.rejectSuggestion(); + }); + }); + + describe('rejectAndDisableSuggestion', () => { + it('will make an http call to apply the suggestion, and then navigate back the curations page', async () => { + http.put.mockReturnValueOnce( + Promise.resolve({ + results: [ + { + ...suggestion, + status: 'disabled', + curation_id: 'cur-6155e69c7a2f2e4f756303fd', + }, + ], + }) + ); + mountLogic({ + suggestion, + }); + + CurationSuggestionLogic.actions.rejectAndDisableSuggestion(); await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledWith('error'); + expect(http.put).toHaveBeenCalledWith( + '/internal/app_search/engines/some-engine/search_relevance_suggestions', + { + body: JSON.stringify([ + { + query: 'foo', + type: 'curation', + status: 'disabled', + }, + ]), + } + ); + + expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); + }); + + itHandlesErrors(http.put, () => { + CurationSuggestionLogic.actions.rejectAndDisableSuggestion(); + }); + + itHandlesInlineErrors(() => { + CurationSuggestionLogic.actions.rejectAndDisableSuggestion(); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts index 3c3af8bfd96c8..6749b510edeba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts @@ -8,12 +8,23 @@ import { kea, MakeLogicType } from 'kea'; import { HttpSetup } from 'kibana/public'; -import { flashAPIErrors } from '../../../../../shared/flash_messages'; +import { i18n } from '@kbn/i18n'; + +import { + flashAPIErrors, + setQueuedErrorMessage, + setQueuedSuccessMessage, +} from '../../../../../shared/flash_messages'; import { HttpLogic } from '../../../../../shared/http'; -import { EngineLogic } from '../../../engine'; +import { KibanaLogic } from '../../../../../shared/kibana'; +import { ENGINE_CURATIONS_PATH, ENGINE_CURATION_PATH } from '../../../../routes'; +import { EngineLogic, generateEnginePath } from '../../../engine'; import { Result } from '../../../result/types'; import { Curation, CurationSuggestion } from '../../types'; +interface Error { + error: string; +} interface CurationSuggestionValues { dataLoading: boolean; suggestion: CurationSuggestion | null; @@ -36,6 +47,10 @@ interface CurationSuggestionActions { suggestedPromotedDocuments: Result[]; curation: Curation; }; + acceptSuggestion(): void; + acceptAndAutomateSuggestion(): void; + rejectSuggestion(): void; + rejectAndDisableSuggestion(): void; } interface CurationSuggestionProps { @@ -53,6 +68,10 @@ export const CurationSuggestionLogic = kea< suggestedPromotedDocuments, curation, }), + acceptSuggestion: true, + acceptAndAutomateSuggestion: true, + rejectSuggestion: true, + rejectAndDisableSuggestion: true, }), reducers: () => ({ dataLoading: [ @@ -81,13 +100,14 @@ export const CurationSuggestionLogic = kea< }, ], }), - listeners: ({ actions, props }) => ({ + listeners: ({ actions, values, props }) => ({ loadSuggestion: async () => { const { http } = HttpLogic.values; const { engineName } = EngineLogic.values; try { - const suggestion = await getSuggestions(http, engineName, props.query); + const suggestion = await getSuggestion(http, engineName, props.query); + if (!suggestion) return; const promotedIds: string[] = suggestion.promoted; const documentDetailsResopnse = getDocumentDetails(http, engineName, promotedIds); @@ -116,14 +136,144 @@ export const CurationSuggestionLogic = kea< flashAPIErrors(e); } }, + acceptSuggestion: async () => { + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + const { suggestion } = values; + + try { + const updatedSuggestion = await updateSuggestion( + http, + engineName, + suggestion!.query, + 'applied' + ); + + setQueuedSuccessMessage( + i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAppliedMessage', + { defaultMessage: 'Suggestion was succefully applied.' } + ) + ); + KibanaLogic.values.navigateToUrl( + generateEnginePath(ENGINE_CURATION_PATH, { + curationId: updatedSuggestion.curation_id, + }) + ); + } catch (e) { + flashAPIErrors(e); + } + }, + acceptAndAutomateSuggestion: async () => { + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + const { suggestion } = values; + + try { + const updatedSuggestion = await updateSuggestion( + http, + engineName, + suggestion!.query, + 'automated' + ); + + setQueuedSuccessMessage( + i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAutomatedMessage', + { + defaultMessage: + 'Suggestion was succefully applied and all future suggestions for the query "{query}" will be automatically applied.', + values: { query: suggestion!.query }, + } + ) + ); + KibanaLogic.values.navigateToUrl( + generateEnginePath(ENGINE_CURATION_PATH, { + curationId: updatedSuggestion.curation_id, + }) + ); + } catch (e) { + flashAPIErrors(e); + } + }, + rejectSuggestion: async () => { + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + const { suggestion } = values; + + try { + await updateSuggestion(http, engineName, suggestion!.query, 'rejected'); + + setQueuedSuccessMessage( + i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyRejectedMessage', + { + defaultMessage: 'Suggestion was succefully rejected.', + } + ) + ); + KibanaLogic.values.navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); + } catch (e) { + flashAPIErrors(e); + } + }, + rejectAndDisableSuggestion: async () => { + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + const { suggestion } = values; + + try { + await updateSuggestion(http, engineName, suggestion!.query, 'disabled'); + + setQueuedSuccessMessage( + i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyDisabledMessage', + { + defaultMessage: + 'Suggestion was succefully rejected and you will no longer receive suggestions for the query "{query}".', + values: { query: suggestion!.query }, + } + ) + ); + KibanaLogic.values.navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); + } catch (e) { + flashAPIErrors(e); + } + }, }), }); -const getSuggestions = async ( +const updateSuggestion = async ( + http: HttpSetup, + engineName: string, + query: string, + status: string +) => { + const response = await http.put<{ results: Array }>( + `/internal/app_search/engines/${engineName}/search_relevance_suggestions`, + { + body: JSON.stringify([ + { + query, + type: 'curation', + status, + }, + ]), + } + ); + + if (response.results[0].hasOwnProperty('error')) { + throw (response.results[0] as Error).error; + } + + return response.results[0] as CurationSuggestion; +}; + +const getSuggestion = async ( http: HttpSetup, engineName: string, query: string -): Promise => { +): Promise => { const response = await http.post( `/internal/app_search/engines/${engineName}/search_relevance_suggestions/${query}`, { @@ -140,6 +290,18 @@ const getSuggestions = async ( } ); + if (response.results.length < 1) { + const message = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.notFoundError', + { + defaultMessage: 'Could not find suggestion, it may have already been applied or rejected.', + } + ); + setQueuedErrorMessage(message); + KibanaLogic.values.navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); + return; + } + const suggestion = response.results[0] as CurationSuggestion; return suggestion; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts index 4fe8ad1cb851d..abaa67e06f606 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts @@ -14,7 +14,7 @@ import { IFlashMessage } from './types'; /** * The API errors we are handling can come from one of two ways: - * - When our http calls recieve a response containing an error code, such as a 404 or 500 + * - When our http calls receive a response containing an error code, such as a 404 or 500 * - Our own JS while handling a successful response * * In the first case, if it is a purposeful error (like a 404) we will receive an diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index cbb59cc3204c0..aba23eef79e3f 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -267,6 +267,42 @@ } } }, + "alert": { + "properties": { + "rule": { + "properties": { + "execution": { + "properties": { + "uuid": { + "type": "keyword", + "ignore_above": 1024 + }, + "status": { + "type": "keyword", + "ignore_above": 1024 + }, + "status_order": { + "type": "long" + }, + "metrics": { + "properties": { + "total_indexing_duration_ms": { + "type": "long" + }, + "total_search_duration_ms": { + "type": "long" + }, + "execution_gap_duration_s": { + "type": "long" + } + } + } + } + } + } + } + } + }, "saved_objects": { "type": "nested", "properties": { @@ -292,6 +328,13 @@ } } }, + "space_ids": { + "type": "keyword", + "ignore_above": 1024, + "meta": { + "isArray": "true" + } + }, "version": { "type": "version" } diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index 15dc182dbe653..e73bafd9cb81e 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -116,6 +116,28 @@ export const EventSchema = schema.maybe( status: ecsString(), }) ), + alert: schema.maybe( + schema.object({ + rule: schema.maybe( + schema.object({ + execution: schema.maybe( + schema.object({ + uuid: ecsString(), + status: ecsString(), + status_order: ecsNumber(), + metrics: schema.maybe( + schema.object({ + total_indexing_duration_ms: ecsNumber(), + total_search_duration_ms: ecsNumber(), + execution_gap_duration_s: ecsNumber(), + }) + ), + }) + ), + }) + ), + }) + ), saved_objects: schema.maybe( schema.arrayOf( schema.object({ @@ -127,6 +149,7 @@ export const EventSchema = schema.maybe( }) ) ), + space_ids: ecsStringMulti(), version: ecsVersion(), }) ), diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index d114603052491..231cc225f7c47 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -49,6 +49,42 @@ exports.EcsCustomPropertyMappings = { }, }, }, + alert: { + properties: { + rule: { + properties: { + execution: { + properties: { + uuid: { + type: 'keyword', + ignore_above: 1024, + }, + status: { + type: 'keyword', + ignore_above: 1024, + }, + status_order: { + type: 'long', + }, + metrics: { + properties: { + total_indexing_duration_ms: { + type: 'long', + }, + total_search_duration_ms: { + type: 'long', + }, + execution_gap_duration_s: { + type: 'long', + }, + }, + }, + }, + }, + }, + }, + }, + }, // array of saved object references, for "linking" via search saved_objects: { type: 'nested', @@ -77,6 +113,10 @@ exports.EcsCustomPropertyMappings = { }, }, }, + space_ids: { + type: 'keyword', + ignore_above: 1024, + }, version: { type: 'version', }, @@ -105,4 +145,10 @@ exports.EcsPropertiesToGenerate = [ /** * These properties can have multiple values (are arrays in the generated event schema). */ -exports.EcsEventLogMultiValuedProperties = ['tags', 'event.category', 'event.type', 'rule.author']; +exports.EcsEventLogMultiValuedProperties = [ + 'tags', + 'event.category', + 'event.type', + 'rule.author', + 'kibana.space_ids', +]; diff --git a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts index 275cf237a9621..e554eb925c38a 100644 --- a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts @@ -33,6 +33,7 @@ describe('Fleet - packageToPackagePolicy', () => { lens: [], ml_module: [], security_rule: [], + tag: [], }, elasticsearch: { ingest_pipeline: [], diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 1325d74f82b68..a487fd0a37e70 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -69,6 +69,7 @@ export enum KibanaAssetType { lens = 'lens', securityRule = 'security_rule', mlModule = 'ml_module', + tag = 'tag', } /* @@ -83,6 +84,7 @@ export enum KibanaSavedObjectType { lens = 'lens', mlModule = 'ml-module', securityRule = 'security-rule', + tag = 'tag', } export enum ElasticsearchAssetType { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index 35092cb67f7ef..4d940534c4a7b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -584,10 +584,17 @@ export const EditPackagePolicyForm = memo<{ fill data-test-subj="saveIntegration" > - + {isUpgrade ? ( + + ) : ( + + )} @@ -680,7 +687,7 @@ const UpgradeStatusCallout: React.FunctionComponent<{

diff --git a/x-pack/plugins/fleet/public/applications/integrations/app.tsx b/x-pack/plugins/fleet/public/applications/integrations/app.tsx index c5cc1e1892eda..b10cef9d3ffe4 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/app.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/app.tsx @@ -39,15 +39,13 @@ import { Error, Loading, SettingFlyout } from './components'; import type { UIExtensionsStorage } from './types'; import { EPMApp } from './sections/epm'; -import { DefaultLayout, WithoutHeaderLayout } from './layouts'; +import { DefaultLayout } from './layouts'; import { PackageInstallProvider } from './hooks'; import { useBreadcrumbs, UIExtensionsContext } from './hooks'; const ErrorLayout = ({ children }: { children: JSX.Element }) => ( - - {children} - + {children} ); diff --git a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx index e4de48a85c35a..70e55c9bd56b0 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx @@ -28,6 +28,8 @@ interface Props { const Illustration = styled(EuiImage)` margin-bottom: -68px; + position: relative; + top: -20px; width: 80%; `; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx index d98f2b2408d56..a7fa069e77a69 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/assets_facet_group.stories.tsx @@ -36,6 +36,7 @@ export const AssetsFacetGroup = ({ width }: Args) => { lens: [], security_rule: [], ml_module: [], + tag: [], }, elasticsearch: { component_template: [], diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx index ac83cc83b6849..109f7500f160b 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx @@ -38,6 +38,7 @@ export interface ListProps { setSelectedCategory: (category: string) => void; onSearchChange: (search: string) => void; showMissingIntegrationMessage?: boolean; + callout?: JSX.Element | null; } export function PackageListGrid({ @@ -49,6 +50,7 @@ export function PackageListGrid({ onSearchChange, setSelectedCategory, showMissingIntegrationMessage = false, + callout, }: ListProps) { const [searchTerm, setSearchTerm] = useState(initialSearch || ''); const localSearchRef = useLocalSearch(list); @@ -105,6 +107,7 @@ export function PackageListGrid({ }} onChange={onQueryChange} /> + {callout ? callout : null} {gridContent} {showMissingIntegrationMessage && ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx index 8e900e625215f..25604bb6b984d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx @@ -6,6 +6,7 @@ */ import type { IconType } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import type { ServiceName } from '../../types'; import { ElasticsearchAssetType, KibanaAssetType } from '../../types'; @@ -22,21 +23,54 @@ export const DisplayedAssets: ServiceNameToAssetTypes = { export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType | 'view'; export const AssetTitleMap: Record = { - dashboard: 'Dashboards', - ilm_policy: 'ILM policies', - ingest_pipeline: 'Ingest pipelines', - transform: 'Transforms', - index_pattern: 'Index patterns', - index_template: 'Index templates', - component_template: 'Component templates', - search: 'Saved searches', - visualization: 'Visualizations', - map: 'Maps', - data_stream_ilm_policy: 'Data stream ILM policies', - lens: 'Lens', - security_rule: 'Security rules', - ml_module: 'ML modules', - view: 'Views', + dashboard: i18n.translate('xpack.fleet.epm.assetTitles.dashboards', { + defaultMessage: 'Dashboards', + }), + ilm_policy: i18n.translate('xpack.fleet.epm.assetTitles.ilmPolicies', { + defaultMessage: 'ILM policies', + }), + ingest_pipeline: i18n.translate('xpack.fleet.epm.assetTitles.ingestPipelines', { + defaultMessage: 'Ingest pipelines', + }), + transform: i18n.translate('xpack.fleet.epm.assetTitles.transforms', { + defaultMessage: 'Transforms', + }), + index_pattern: i18n.translate('xpack.fleet.epm.assetTitles.indexPatterns', { + defaultMessage: 'Index patterns', + }), + index_template: i18n.translate('xpack.fleet.epm.assetTitles.indexTemplates', { + defaultMessage: 'Index templates', + }), + component_template: i18n.translate('xpack.fleet.epm.assetTitles.componentTemplates', { + defaultMessage: 'Component templates', + }), + search: i18n.translate('xpack.fleet.epm.assetTitles.savedSearches', { + defaultMessage: 'Saved searches', + }), + visualization: i18n.translate('xpack.fleet.epm.assetTitles.visualizations', { + defaultMessage: 'Visualizations', + }), + map: i18n.translate('xpack.fleet.epm.assetTitles.maps', { + defaultMessage: 'Maps', + }), + data_stream_ilm_policy: i18n.translate('xpack.fleet.epm.assetTitles.dataStreamILM', { + defaultMessage: 'Data stream ILM policies', + }), + lens: i18n.translate('xpack.fleet.epm.assetTitles.lens', { + defaultMessage: 'Lens', + }), + security_rule: i18n.translate('xpack.fleet.epm.assetTitles.securityRules', { + defaultMessage: 'Security rules', + }), + ml_module: i18n.translate('xpack.fleet.epm.assetTitles.mlModules', { + defaultMessage: 'ML modules', + }), + view: i18n.translate('xpack.fleet.epm.assetTitles.views', { + defaultMessage: 'Views', + }), + tag: i18n.translate('xpack.fleet.epm.assetTitles.tag', { + defaultMessage: 'Tag', + }), }; export const ServiceTitleMap: Record = { @@ -53,6 +87,7 @@ export const AssetIcons: Record = { lens: 'lensApp', security_rule: 'securityApp', ml_module: 'mlApp', + tag: 'tagApp', }; export const ServiceIcons: Record = { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index 62225d14d3857..06cf85699bf67 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -5,10 +5,13 @@ * 2.0. */ -import React, { memo, useMemo } from 'react'; +import React, { memo, useMemo, Fragment } from 'react'; import { Switch, Route, useLocation, useHistory, useParams } from 'react-router-dom'; import semverLt from 'semver/functions/lt'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import { installationStatuses } from '../../../../../../../common/constants'; import type { DynamicPage, DynamicPagePathValues, StaticPage } from '../../../../constants'; @@ -24,6 +27,7 @@ import { useGetAppendCustomIntegrations, useGetReplacementCustomIntegrations, useLink, + useStartServices, } from '../../../../hooks'; import { doesPackageHaveIntegrations } from '../../../../services'; import { DefaultLayout } from '../../../../layouts'; @@ -143,6 +147,7 @@ const InstalledPackages: React.FC = memo(() => { experimental: true, }); const { getHref, getAbsolutePath } = useLink(); + const { docLinks } = useStartServices(); const { selectedCategory, searchParam } = getParams( useParams(), @@ -225,6 +230,38 @@ const InstalledPackages: React.FC = memo(() => { return mapToCard(getAbsolutePath, getHref, item); }); + const link = ( + + {i18n.translate('xpack.fleet.epmList.availableCalloutBlogText', { + defaultMessage: 'announcement blog post', + })} + + ); + const calloutMessage = ( + + ); + + const callout = + selectedCategory === 'updates_available' ? null : ( + + + +

{calloutMessage}

+
+
+ ); + return ( { initialSearch={searchParam} title={title} list={cards} + callout={callout} /> ); }); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.test.ts index 30f2909b2403e..feab9cb882393 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.test.ts @@ -46,6 +46,11 @@ describe('buildDefaultSettings', () => { "lifecycle": Object { "name": "logs", }, + "mapping": Object { + "total_fields": Object { + "limit": "10000", + }, + }, "query": Object { "default_field": Array [ "field1Keyword", diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.ts index a9ae65e9e97b8..84ec75b9da065 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.ts @@ -67,6 +67,11 @@ export function buildDefaultSettings({ }, // What should be our default for the compression? codec: 'best_compression', + mapping: { + total_fields: { + limit: '10000', + }, + }, // All the default fields which should be queried have to be added here. // So far we add all keyword and text fields here if there are any, otherwise diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts index 0f2d7b6679bf9..50c0239cd8c56 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts @@ -39,6 +39,7 @@ const KibanaSavedObjectTypeMapping: Record { diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts index 2dff3f43f3ec1..aa2c3f1d4da3c 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts @@ -96,16 +96,12 @@ export async function fetchList(params?: SearchParams): Promise { const registryUrl = getRegistryUrl(); - const kibanaVersion = appContextService.getKibanaVersion().split('-')[0]; // may be x.y.z-SNAPSHOT - const kibanaBranch = appContextService.getKibanaBranch(); const url = new URL( `${registryUrl}/search?package=${packageName}&internal=true&experimental=true` ); - // on master, request all packages regardless of version - if (kibanaVersion && kibanaBranch !== 'master') { - url.searchParams.set('kibana.version', kibanaVersion); - } + setKibanaVersion(url); + const res = await fetchUrl(url.toString()); const searchResults = JSON.parse(res); if (searchResults.length) { diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts index 9f8ac01afe6c9..845e4f1d2670e 100644 --- a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts @@ -97,6 +97,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { lens: [], security_rule: [], ml_module: [], + tag: [], }, elasticsearch: { component_template: [], @@ -207,6 +208,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { lens: [], security_rule: [], ml_module: [], + tag: [], }, elasticsearch: { component_template: [], @@ -323,6 +325,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { lens: [], security_rule: [], ml_module: [], + tag: [], }, elasticsearch: { component_template: [], diff --git a/x-pack/plugins/fleet/storybook/context/doc_links.ts b/x-pack/plugins/fleet/storybook/context/doc_links.ts new file mode 100644 index 0000000000000..56287dd9116a9 --- /dev/null +++ b/x-pack/plugins/fleet/storybook/context/doc_links.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DocLinksStart } from 'kibana/public'; + +export const getDocLinks = () => { + const docLinks: DocLinksStart = { + links: { + fleet: { + learnMoreBlog: + 'https://www.elastic.co/blog/elastic-agent-and-fleet-make-it-easier-to-integrate-your-systems-with-elastic', + }, + }, + } as unknown as DocLinksStart; + + return docLinks; +}; diff --git a/x-pack/plugins/fleet/storybook/context/fixtures/integration.nginx.ts b/x-pack/plugins/fleet/storybook/context/fixtures/integration.nginx.ts index 50262b73a6a41..e0179897a59c7 100644 --- a/x-pack/plugins/fleet/storybook/context/fixtures/integration.nginx.ts +++ b/x-pack/plugins/fleet/storybook/context/fixtures/integration.nginx.ts @@ -252,6 +252,7 @@ export const response: GetInfoResponse['response'] = { lens: [], map: [], security_rule: [], + tag: [], }, elasticsearch: { ingest_pipeline: [ diff --git a/x-pack/plugins/fleet/storybook/context/fixtures/integration.okta.ts b/x-pack/plugins/fleet/storybook/context/fixtures/integration.okta.ts index efef00579f4bd..387161171485b 100644 --- a/x-pack/plugins/fleet/storybook/context/fixtures/integration.okta.ts +++ b/x-pack/plugins/fleet/storybook/context/fixtures/integration.okta.ts @@ -105,6 +105,7 @@ export const response: GetInfoResponse['response'] = { lens: [], ml_module: [], security_rule: [], + tag: [], }, elasticsearch: { ingest_pipeline: [ diff --git a/x-pack/plugins/fleet/storybook/context/index.tsx b/x-pack/plugins/fleet/storybook/context/index.tsx index e5a360c28385b..6d563346e917d 100644 --- a/x-pack/plugins/fleet/storybook/context/index.tsx +++ b/x-pack/plugins/fleet/storybook/context/index.tsx @@ -27,6 +27,7 @@ import { getHttp } from './http'; import { getUiSettings } from './ui_settings'; import { getNotifications } from './notifications'; import { stubbedStartServices } from './stubs'; +import { getDocLinks } from './doc_links'; // TODO: clintandrewhall - this is not ideal, or complete. The root context of Fleet applications // requires full start contracts of its dependencies. As a result, we have to mock all of those contracts @@ -42,8 +43,10 @@ export const StorybookContext: React.FC<{ storyContext?: StoryContext }> = ({ const history = new ScopedHistory(browserHistory, basepath); const startServices: FleetStartServices = { + ...stubbedStartServices, application: getApplication(), chrome: getChrome(), + docLinks: getDocLinks(), http: getHttp(), notifications: getNotifications(), uiSettings: getUiSettings(), @@ -58,7 +61,6 @@ export const StorybookContext: React.FC<{ storyContext?: StoryContext }> = ({ customIntegrations: { ContextProvider: getStorybookContextProvider(), }, - ...stubbedStartServices, }; setHttpClient(startServices.http); diff --git a/x-pack/plugins/fleet/storybook/context/stubs.tsx b/x-pack/plugins/fleet/storybook/context/stubs.tsx index 54ae18b083a2f..a7db4bd8f68cd 100644 --- a/x-pack/plugins/fleet/storybook/context/stubs.tsx +++ b/x-pack/plugins/fleet/storybook/context/stubs.tsx @@ -9,7 +9,6 @@ import type { FleetStartServices } from '../../public/plugin'; type Stubs = | 'storage' - | 'docLinks' | 'data' | 'deprecations' | 'fatalErrors' @@ -22,7 +21,6 @@ type StubbedStartServices = Pick; export const stubbedStartServices: StubbedStartServices = { storage: {} as FleetStartServices['storage'], - docLinks: {} as FleetStartServices['docLinks'], data: {} as FleetStartServices['data'], deprecations: {} as FleetStartServices['deprecations'], fatalErrors: {} as FleetStartServices['fatalErrors'], diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts b/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts index 47bb1f91b4ab2..9ff1b5a4dc3f7 100644 --- a/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts +++ b/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts @@ -41,6 +41,7 @@ export interface YConfig { lineStyle?: LineStyle; fill?: FillStyle; iconPosition?: IconPosition; + textVisibility?: boolean; } export type AxisTitlesVisibilityConfigResult = AxesSettingsConfig & { @@ -187,6 +188,10 @@ export const yAxisConfig: ExpressionFunctionDefinition< options: ['auto', 'above', 'below', 'left', 'right'], help: 'The placement of the icon for the threshold line', }, + textVisibility: { + types: ['boolean'], + help: 'Visibility of the label on the threshold line', + }, fill: { types: ['string'], options: ['none', 'above', 'below'], diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx index b0c10abb75810..e052e06f1b2f1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx @@ -57,7 +57,7 @@ export function AddLayerButton({ })} content={i18n.translate('xpack.lens.xyChart.addLayerTooltip', { defaultMessage: - 'Use multiple layers to combine visualization types or visualize different data views.', + 'Use multiple layers to combine visualization types or visualize different index patterns.', })} position="bottom" > diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index f1161b83c228e..e4816870b4380 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -151,9 +151,9 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ ? [ { shortMessage: '', - longMessage: i18n.translate('xpack.lens.indexPattern.missingDataView', { + longMessage: i18n.translate('xpack.lens.indexPattern.missingIndexPattern', { defaultMessage: - 'The {count, plural, one {data view} other {data views}} ({count, plural, one {id} other {ids}}: {indexpatterns}) cannot be found', + 'The {count, plural, one {index pattern} other {index patterns}} ({count, plural, one {id} other {ids}}: {indexpatterns}) cannot be found', values: { count: missingIndexPatterns.length, indexpatterns: missingIndexPatterns.join(', '), @@ -569,8 +569,8 @@ export const VisualizationWrapper = ({ })} data-test-subj="configuration-failure-reconfigure-indexpatterns" > - {i18n.translate('xpack.lens.editorFrame.dataViewReconfigure', { - defaultMessage: `Recreate it in the data view management page`, + {i18n.translate('xpack.lens.editorFrame.indexPatternReconfigure', { + defaultMessage: `Recreate it in the index pattern management page`, })} @@ -580,8 +580,8 @@ export const VisualizationWrapper = ({ <>

diff --git a/x-pack/plugins/lens/public/editor_frame_service/error_helper.ts b/x-pack/plugins/lens/public/editor_frame_service/error_helper.ts index 9df48d99ce762..b19a295b68407 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/error_helper.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/error_helper.ts @@ -160,8 +160,9 @@ export function getMissingCurrentDatasource() { } export function getMissingIndexPatterns(indexPatternIds: string[]) { - return i18n.translate('xpack.lens.editorFrame.expressionMissingDataView', { - defaultMessage: 'Could not find the {count, plural, one {data view} other {data views}}: {ids}', + return i18n.translate('xpack.lens.editorFrame.expressionMissingIndexPattern', { + defaultMessage: + 'Could not find the {count, plural, one {index pattern} other {index pattern}}: {ids}', values: { count: indexPatternIds.length, ids: indexPatternIds.join(', ') }, }); } diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx index eea54ed525f6c..4c247c031eac0 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx @@ -236,7 +236,7 @@ describe('embeddable', () => { ...savedVis, sharingSavedObjectProps: { outcome: 'conflict', - errorJSON: '{targetType: "lens", sourceId: "1", targetSpace: "space"}', + sourceId: '1', aliasTargetId: '2', }, } as ResolvedLensSavedObjectAttributes); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 2e0ab2401c70f..7faf873cf0b0a 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -286,8 +286,9 @@ export class Embeddable defaultMessage: `You've encountered a URL conflict`, }), longMessage: ( - ), }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx index ca44e833981ab..64d7f5efc9c4d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx @@ -69,8 +69,8 @@ export function ChangeIndexPattern({ >

- {i18n.translate('xpack.lens.indexPattern.changeDataViewTitle', { - defaultMessage: 'Data view', + {i18n.translate('xpack.lens.indexPattern.changeIndexPatternTitle', { + defaultMessage: 'Index pattern', })}

@@ -642,7 +642,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ iconType="boxesHorizontal" data-test-subj="lnsIndexPatternActions" aria-label={i18n.translate('xpack.lens.indexPatterns.actionsPopoverLabel', { - defaultMessage: 'Data view settings', + defaultMessage: 'Index pattern settings', })} onClick={() => { setPopoverOpen(!popoverOpen); @@ -663,7 +663,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ }} > {i18n.translate('xpack.lens.indexPatterns.addFieldButton', { - defaultMessage: 'Add field to data view', + defaultMessage: 'Add field to index pattern', })} , {i18n.translate('xpack.lens.indexPatterns.manageFieldButton', { - defaultMessage: 'Manage data view fields', + defaultMessage: 'Manage index pattern fields', })} , ]} @@ -709,7 +709,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ data-test-subj="lnsIndexPatternFieldSearch" placeholder={i18n.translate('xpack.lens.indexPatterns.filterByNameLabel', { defaultMessage: 'Search field names', - description: 'Search the list of fields in the data view for the provided text', + description: 'Search the list of fields in the index pattern for the provided text', })} value={localState.nameFilter} onChange={(e) => { @@ -717,7 +717,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ }} aria-label={i18n.translate('xpack.lens.indexPatterns.filterByNameLabel', { defaultMessage: 'Search field names', - description: 'Search the list of fields in the data view for the provided text', + description: 'Search the list of fields in the index pattern for the provided text', })} aria-describedby={fieldSearchDescriptionId} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 29bbe6a96b9e1..2f1c00bc5cca0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -107,16 +107,19 @@ export function DimensionEditor(props: DimensionEditorProps) { ); const setStateWrapper = ( - setter: IndexPatternLayer | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) + setter: IndexPatternLayer | ((prevLayer: IndexPatternLayer) => IndexPatternLayer), + options: { forceRender?: boolean } = {} ) => { const hypotheticalLayer = typeof setter === 'function' ? setter(state.layers[layerId]) : setter; + const isDimensionComplete = Boolean(hypotheticalLayer.columns[columnId]); setState( (prevState) => { const layer = typeof setter === 'function' ? setter(prevState.layers[layerId]) : setter; return mergeLayer({ state: prevState, layerId, newLayer: layer }); }, { - isDimensionComplete: Boolean(hypotheticalLayer.columns[columnId]), + isDimensionComplete, + ...options, } ); }; @@ -169,20 +172,8 @@ export function DimensionEditor(props: DimensionEditorProps) { ) => { if (temporaryStaticValue) { setTemporaryState('none'); - if (typeof setter === 'function') { - return setState( - (prevState) => { - const layer = setter(addStaticValueColumn(prevState.layers[layerId])); - return mergeLayer({ state: prevState, layerId, newLayer: layer }); - }, - { - isDimensionComplete: true, - forceRender: true, - } - ); - } } - return setStateWrapper(setter); + return setStateWrapper(setter, { forceRender: true }); }; const ParamEditor = getParamEditor( @@ -314,7 +305,7 @@ export function DimensionEditor(props: DimensionEditorProps) { temporaryQuickFunction && isQuickFunction(newLayer.columns[columnId].operationType) ) { - // Only switch the tab once the formula is fully removed + // Only switch the tab once the "non quick function" is fully removed setTemporaryState('none'); } setStateWrapper(newLayer); @@ -344,13 +335,12 @@ export function DimensionEditor(props: DimensionEditorProps) { visualizationGroups: dimensionGroups, targetGroup: props.groupId, }); - // ); } if ( temporaryQuickFunction && isQuickFunction(newLayer.columns[columnId].operationType) ) { - // Only switch the tab once the formula is fully removed + // Only switch the tab once the "non quick function" is fully removed setTemporaryState('none'); } setStateWrapper(newLayer); @@ -508,6 +498,9 @@ export function DimensionEditor(props: DimensionEditorProps) { } incompleteOperation={incompleteOperation} onChoose={(choice) => { + if (temporaryQuickFunction) { + setTemporaryState('none'); + } setStateWrapper( insertOrReplaceColumn({ layer: state.layers[layerId], @@ -518,7 +511,8 @@ export function DimensionEditor(props: DimensionEditorProps) { visualizationGroups: dimensionGroups, targetGroup: props.groupId, incompleteParams, - }) + }), + { forceRender: temporaryQuickFunction } ); }} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 6df4360aeac4c..6d9e1ae3fe81b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -513,7 +513,10 @@ describe('IndexPatternDimensionEditorPanel', () => { comboBox.prop('onChange')!([option]); }); - expect(setState.mock.calls[0]).toEqual([expect.any(Function), { isDimensionComplete: true }]); + expect(setState.mock.calls[0]).toEqual([ + expect.any(Function), + { isDimensionComplete: true, forceRender: false }, + ]); expect(setState.mock.calls[0][0](defaultProps.state)).toEqual({ ...initialState, layers: { @@ -545,7 +548,10 @@ describe('IndexPatternDimensionEditorPanel', () => { comboBox.prop('onChange')!([option]); }); - expect(setState.mock.calls[0]).toEqual([expect.any(Function), { isDimensionComplete: true }]); + expect(setState.mock.calls[0]).toEqual([ + expect.any(Function), + { isDimensionComplete: true, forceRender: false }, + ]); expect(setState.mock.calls[0][0](defaultProps.state)).toEqual({ ...state, layers: { @@ -1037,7 +1043,10 @@ describe('IndexPatternDimensionEditorPanel', () => { }); expect(setState.mock.calls.length).toEqual(2); - expect(setState.mock.calls[1]).toEqual([expect.any(Function), { isDimensionComplete: true }]); + expect(setState.mock.calls[1]).toEqual([ + expect.any(Function), + { isDimensionComplete: true, forceRender: false }, + ]); expect(setState.mock.calls[1][0](state)).toEqual({ ...state, layers: { @@ -1921,7 +1930,10 @@ describe('IndexPatternDimensionEditorPanel', () => { comboBox.prop('onChange')!([option]); }); - expect(setState.mock.calls[0]).toEqual([expect.any(Function), { isDimensionComplete: true }]); + expect(setState.mock.calls[0]).toEqual([ + expect.any(Function), + { isDimensionComplete: true, forceRender: false }, + ]); expect(setState.mock.calls[0][0](defaultProps.state)).toEqual({ ...state, layers: { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimensions_editor_helpers.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimensions_editor_helpers.tsx index dc6dc6dc31c86..a39f3705fd230 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimensions_editor_helpers.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimensions_editor_helpers.tsx @@ -217,7 +217,7 @@ export function getErrorMessage( } if (fieldInvalid) { return i18n.translate('xpack.lens.indexPattern.invalidFieldLabel', { - defaultMessage: 'Invalid field. Check your data view or pick another field.', + defaultMessage: 'Invalid field. Check your index pattern or pick another field.', }); } } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index ee6065aabf9d1..9c22ec9d4bb05 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -348,7 +348,7 @@ function FieldPanelHeader({ @@ -366,7 +366,7 @@ function FieldPanelHeader({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index c408d0130825b..2138b06a4c344 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -98,8 +98,8 @@ export function getIndexPatternDatasource({ const uiSettings = core.uiSettings; const onIndexPatternLoadError = (err: Error) => core.notifications.toasts.addError(err, { - title: i18n.translate('xpack.lens.indexPattern.dataViewLoadError', { - defaultMessage: 'Error loading data view', + title: i18n.translate('xpack.lens.indexPattern.indexPatternLoadError', { + defaultMessage: 'Error loading index pattern', }), }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.tsx index 28f2921ccc771..12536e556f306 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.tsx @@ -23,8 +23,8 @@ export function LayerPanel({ state, layerId, onChangeIndexPattern }: IndexPatter const indexPattern = state.indexPatterns[layer.indexPatternId]; - const notFoundTitleLabel = i18n.translate('xpack.lens.layerPanel.missingDataView', { - defaultMessage: 'Data view not found', + const notFoundTitleLabel = i18n.translate('xpack.lens.layerPanel.missingIndexPattern', { + defaultMessage: 'Index pattern not found', }); return ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/no_fields_callout.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/no_fields_callout.test.tsx index 635c06691a733..69dc150922b4a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/no_fields_callout.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/no_fields_callout.test.tsx @@ -16,7 +16,7 @@ describe('NoFieldCallout', () => { `); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/no_fields_callout.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/no_fields_callout.tsx index 073b21c700ccc..6b434e8cd41a6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/no_fields_callout.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/no_fields_callout.tsx @@ -32,7 +32,7 @@ export const NoFieldsCallout = ({ size="s" color="warning" title={i18n.translate('xpack.lens.indexPatterns.noFieldsLabel', { - defaultMessage: 'No fields exist in this data view.', + defaultMessage: 'No fields exist in this index pattern.', })} /> ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx index d0dd8a438ed1c..77af42ab41888 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx @@ -343,7 +343,7 @@ describe('last_value', () => { 'data' ); expect(disabledStatus).toEqual( - 'This function requires the presence of a date field in your data view' + 'This function requires the presence of a date field in your index' ); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx index 9a3ba9a044148..88c9d82092e21 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx @@ -134,7 +134,7 @@ export const lastValueOperation: OperationDefinition @@ -284,7 +284,7 @@ export const lastValueOperation: OperationDefinition; }): Promise< - { doc: Document; sharingSavedObjectProps: Omit } | undefined + { doc: Document; sharingSavedObjectProps: Omit } | undefined > => { const { notifications, spaces, attributeService } = lensServices; let doc: Document; diff --git a/x-pack/plugins/lens/public/state_management/types.ts b/x-pack/plugins/lens/public/state_management/types.ts index c15b29d0040d0..e46c685925b20 100644 --- a/x-pack/plugins/lens/public/state_management/types.ts +++ b/x-pack/plugins/lens/public/state_management/types.ts @@ -43,7 +43,7 @@ export interface LensAppState extends EditorFrameState { savedQuery?: SavedQuery; searchSessionId: string; resolvedDateRange: DateRange; - sharingSavedObjectProps?: Omit; + sharingSavedObjectProps?: Omit; } export type DispatchSetState = (state: Partial) => { diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 2e7876f83fc41..87e2762149acd 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -834,5 +834,5 @@ export interface ILensInterpreterRenderHandlers extends IInterpreterRenderHandle export interface SharingSavedObjectProps { outcome?: 'aliasMatch' | 'exactMatch' | 'conflict'; aliasTargetId?: string; - errorJSON?: string; + sourceId?: string; } diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 484032e5ffbd9..5dfad58f50018 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -924,6 +924,7 @@ export function XYChart({ right: Boolean(yAxesMap.right), }} isHorizontal={shouldRotate} + thresholdPaddingMap={thresholdPaddings} /> ) : null} diff --git a/x-pack/plugins/lens/public/xy_visualization/expression_thresholds.scss b/x-pack/plugins/lens/public/xy_visualization/expression_thresholds.scss new file mode 100644 index 0000000000000..41b30ce40676b --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/expression_thresholds.scss @@ -0,0 +1,18 @@ +.lnsXyDecorationRotatedWrapper { + display: inline-block; + overflow: hidden; + line-height: $euiLineHeight; + + .lnsXyDecorationRotatedWrapper__label { + display: inline-block; + white-space: nowrap; + transform: translate(0, 100%) rotate(-90deg); + transform-origin: 0 0; + + &::after { + content: ''; + float: left; + margin-top: 100%; + } + } +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/xy_visualization/expression_thresholds.tsx b/x-pack/plugins/lens/public/xy_visualization/expression_thresholds.tsx index 7532d41f091d1..67e994b734b84 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression_thresholds.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression_thresholds.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import './expression_thresholds.scss'; import React from 'react'; import { groupBy } from 'lodash'; import { EuiIcon } from '@elastic/eui'; @@ -14,8 +15,9 @@ import type { FieldFormat } from 'src/plugins/field_formats/common'; import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import type { LayerArgs, YConfig } from '../../common/expressions'; import type { LensMultiTable } from '../../common/types'; +import { hasIcon } from './xy_config_panel/threshold_panel'; -const THRESHOLD_ICON_SIZE = 20; +const THRESHOLD_MARKER_SIZE = 20; export const computeChartMargins = ( thresholdPaddings: Partial>, @@ -51,27 +53,35 @@ export const computeChartMargins = ( return result; }; -function hasIcon(icon: string | undefined): icon is string { - return icon != null && icon !== 'none'; -} - // Note: it does not take into consideration whether the threshold is in view or not export const getThresholdRequiredPaddings = ( thresholdLayers: LayerArgs[], axesMap: Record<'left' | 'right', unknown> ) => { - const positions = Object.keys(Position); - return thresholdLayers.reduce((memo, layer) => { - if (positions.some((pos) => !(pos in memo))) { - layer.yConfig?.forEach(({ axisMode, icon, iconPosition }) => { - if (axisMode && hasIcon(icon)) { - const placement = getBaseIconPlacement(iconPosition, axisMode, axesMap); - memo[placement] = THRESHOLD_ICON_SIZE; - } - }); + // collect all paddings for the 4 axis: if any text is detected double it. + const paddings: Partial> = {}; + const icons: Partial> = {}; + thresholdLayers.forEach((layer) => { + layer.yConfig?.forEach(({ axisMode, icon, iconPosition, textVisibility }) => { + if (axisMode && (hasIcon(icon) || textVisibility)) { + const placement = getBaseIconPlacement(iconPosition, axisMode, axesMap); + paddings[placement] = Math.max( + paddings[placement] || 0, + THRESHOLD_MARKER_SIZE * (textVisibility ? 2 : 1) // double the padding size if there's text + ); + icons[placement] = (icons[placement] || 0) + (hasIcon(icon) ? 1 : 0); + } + }); + }); + // post-process the padding based on the icon presence: + // if no icon is present for the placement, just reduce the padding + (Object.keys(paddings) as Position[]).forEach((placement) => { + if (!icons[placement]) { + paddings[placement] = THRESHOLD_MARKER_SIZE; } - return memo; - }, {} as Partial>); + }); + + return paddings; }; function mapVerticalToHorizontalPlacement(placement: Position) { @@ -117,17 +127,57 @@ function getBaseIconPlacement( return Position.Top; } -function getIconPlacement( - iconPosition: YConfig['iconPosition'], - axisMode: YConfig['axisMode'], - axesMap: Record, - isHorizontal: boolean -) { - const vPosition = getBaseIconPlacement(iconPosition, axisMode, axesMap); +function getMarkerBody(label: string | undefined, isHorizontal: boolean) { + if (!label) { + return; + } if (isHorizontal) { - return mapVerticalToHorizontalPlacement(vPosition); + return ( +
+ {label} +
+ ); + } + return ( +
+
+ {label} +
+
+ ); +} + +function getMarkerToShow( + yConfig: YConfig, + label: string | undefined, + isHorizontal: boolean, + hasReducedPadding: boolean +) { + // show an icon if present + if (hasIcon(yConfig.icon)) { + return ; + } + // if there's some text, check whether to show it as marker, or just show some padding for the icon + if (yConfig.textVisibility) { + if (hasReducedPadding) { + return getMarkerBody( + label, + (!isHorizontal && yConfig.axisMode === 'bottom') || + (isHorizontal && yConfig.axisMode !== 'bottom') + ); + } + return ; } - return vPosition; } export const ThresholdAnnotations = ({ @@ -138,6 +188,7 @@ export const ThresholdAnnotations = ({ syncColors, axesMap, isHorizontal, + thresholdPaddingMap, }: { thresholdLayers: LayerArgs[]; data: LensMultiTable; @@ -146,6 +197,7 @@ export const ThresholdAnnotations = ({ syncColors: boolean; axesMap: Record<'left' | 'right', boolean>; isHorizontal: boolean; + thresholdPaddingMap: Partial>; }) => { return ( <> @@ -180,15 +232,35 @@ export const ThresholdAnnotations = ({ const defaultColor = euiLightVars.euiColorDarkShade; + // get the position for vertical chart + const markerPositionVertical = getBaseIconPlacement( + yConfig.iconPosition, + yConfig.axisMode, + axesMap + ); + // the padding map is built for vertical chart + const hasReducedPadding = + thresholdPaddingMap[markerPositionVertical] === THRESHOLD_MARKER_SIZE; + const props = { groupId, - marker: hasIcon(yConfig.icon) ? : undefined, - markerPosition: getIconPlacement( - yConfig.iconPosition, - yConfig.axisMode, - axesMap, - isHorizontal + marker: getMarkerToShow( + yConfig, + columnToLabelMap[yConfig.forAccessor], + isHorizontal, + hasReducedPadding + ), + markerBody: getMarkerBody( + yConfig.textVisibility && !hasReducedPadding + ? columnToLabelMap[yConfig.forAccessor] + : undefined, + (!isHorizontal && yConfig.axisMode === 'bottom') || + (isHorizontal && yConfig.axisMode !== 'bottom') ), + // rotate the position if required + markerPosition: isHorizontal + ? mapVerticalToHorizontalPlacement(markerPositionVertical) + : markerPositionVertical, }; const annotations = []; diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index bb65b69a8d121..96ea9b84dd983 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -13,6 +13,7 @@ import { OperationMetadata, DatasourcePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; import type { ValidLayer, XYLayerConfig } from '../../common/expressions'; import { layerTypes } from '../../common'; +import { hasIcon } from './xy_config_panel/threshold_panel'; import { defaultThresholdColor } from './color_assignment'; export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: XYLayerConfig) => { @@ -66,6 +67,7 @@ export function toPreviewExpression( ...config, lineWidth: 1, icon: undefined, + textVisibility: false, })), } ), @@ -344,8 +346,12 @@ export const buildExpression = ( lineStyle: [yConfig.lineStyle || 'solid'], lineWidth: [yConfig.lineWidth || 1], fill: [yConfig.fill || 'none'], - icon: yConfig.icon ? [yConfig.icon] : [], - iconPosition: [yConfig.iconPosition || 'auto'], + icon: hasIcon(yConfig.icon) ? [yConfig.icon] : [], + iconPosition: + hasIcon(yConfig.icon) || yConfig.textVisibility + ? [yConfig.iconPosition || 'auto'] + : ['auto'], + textVisibility: [yConfig.textVisibility || false], }, }, ], diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/threshold_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/threshold_panel.tsx index cdf5bb2cc2ef1..7c31d72e6cbde 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/threshold_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/threshold_panel.tsx @@ -8,7 +8,14 @@ import './xy_config_panel.scss'; import React, { useCallback, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonGroup, EuiComboBox, EuiFormRow, EuiIcon, EuiRange } from '@elastic/eui'; +import { + EuiButtonGroup, + EuiComboBox, + EuiFormRow, + EuiIcon, + EuiRange, + EuiSwitch, +} from '@elastic/eui'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import type { VisualizationDimensionEditorProps } from '../../types'; import { State, XYState } from '../types'; @@ -177,6 +184,10 @@ function getIconPositionOptions({ return [...options, ...yOptions]; } +export function hasIcon(icon: string | undefined): icon is string { + return icon != null && icon !== 'none'; +} + export const ThresholdPanel = ( props: VisualizationDimensionEditorProps & { formatFactory: FormatFactory; @@ -220,6 +231,78 @@ export const ThresholdPanel = ( return ( <> + + { + setYConfig({ forAccessor: accessor, textVisibility: !currentYConfig?.textVisibility }); + }} + /> + + + { + setYConfig({ forAccessor: accessor, icon: newIcon }); + }} + /> + + + + { + const newMode = id.replace(idPrefix, '') as IconPosition; + setYConfig({ forAccessor: accessor, iconPosition: newMode }); + }} + /> + + - - { - setYConfig({ forAccessor: accessor, icon: newIcon }); - }} - /> - - - - { - const newMode = id.replace(idPrefix, '') as IconPosition; - setYConfig({ forAccessor: accessor, iconPosition: newMode }); - }} - /> - - ); }; diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index 88e8e600aa906..7103e395eabdc 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -56,7 +56,7 @@ export async function initFieldsRoute(setup: CoreSetup) { const field = indexPattern.fields.find((f) => f.name === fieldName); if (!field) { - throw new Error(`Field {fieldName} not found in data view ${indexPattern.title}`); + throw new Error(`Field {fieldName} not found in index pattern ${indexPattern.title}`); } const filter = timeFieldName diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index b0daace7afa9e..d15a3efc5375d 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -364,8 +364,9 @@ export class MapEmbeddable iconType="alert" iconColor="danger" data-test-subj="embeddable-maps-failure" - body={spaces.ui.components.getSavedObjectConflictMessage({ - json: sharingSavedObjectProps.errorJSON!, + body={spaces.ui.components.getEmbeddableLegacyUrlConflict({ + targetType: MAP_SAVED_OBJECT_TYPE, + sourceId: sharingSavedObjectProps.sourceId!, })} />
diff --git a/x-pack/plugins/maps/public/map_attribute_service.ts b/x-pack/plugins/maps/public/map_attribute_service.ts index ab380ca5a6b66..85d4d73da82cd 100644 --- a/x-pack/plugins/maps/public/map_attribute_service.ts +++ b/x-pack/plugins/maps/public/map_attribute_service.ts @@ -14,12 +14,11 @@ import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/sav import { getCoreOverlays, getEmbeddableService, getSavedObjectsClient } from './kibana_services'; import { extractReferences, injectReferences } from '../common/migrations/references'; import { MapByValueInput, MapByReferenceInput } from './embeddable/types'; -import { getSpacesApi } from './kibana_services'; export interface SharingSavedObjectProps { outcome?: 'aliasMatch' | 'exactMatch' | 'conflict'; aliasTargetId?: string; - errorJSON?: string; + sourceId?: string; } type MapDoc = MapSavedObjectAttributes & { @@ -88,14 +87,7 @@ export function getMapAttributeService(): MapAttributeService { sharingSavedObjectProps: { aliasTargetId, outcome, - errorJSON: - outcome === 'conflict' && getSpacesApi() - ? JSON.stringify({ - targetType: MAP_SAVED_OBJECT_TYPE, - sourceId: savedObjectId, - targetSpace: (await getSpacesApi()!.getActiveSpace()).id, - }) - : undefined, + sourceId: savedObjectId, }, }; }, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_name.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_name.test.tsx index 696eeefa6cc1a..6b26e3823d2ef 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_name.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_name.test.tsx @@ -69,7 +69,7 @@ describe('DeleteAction', () => { }); describe('When delete model is open', () => { - it('should allow to delete target index by default.', () => { + it('should not allow to delete target index by default.', () => { const mock = jest.spyOn(CheckPrivilige, 'checkPermission'); mock.mockImplementation((p) => p === 'canDeleteDataFrameAnalytics'); @@ -101,10 +101,9 @@ describe('DeleteAction', () => { const deleteButton = getByTestId('mlAnalyticsJobDeleteButton'); fireEvent.click(deleteButton); expect(getByTestId('mlAnalyticsJobDeleteModal')).toBeInTheDocument(); - expect(getByTestId('mlAnalyticsJobDeleteIndexSwitch')).toBeInTheDocument(); - const mlAnalyticsJobDeleteIndexSwitch = getByTestId('mlAnalyticsJobDeleteIndexSwitch'); - expect(mlAnalyticsJobDeleteIndexSwitch).toHaveAttribute('aria-checked', 'true'); + expect(queryByTestId('mlAnalyticsJobDeleteIndexSwitch')).toBeNull(); expect(queryByTestId('mlAnalyticsJobDeleteIndexPatternSwitch')).toBeNull(); + mock.mockRestore(); }); }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.tsx index 4abe70435d37c..91871015d2add 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.tsx @@ -89,9 +89,9 @@ export const useDeleteAction = (canDeleteDataFrameAnalytics: boolean) => { ); } }; - const checkUserIndexPermission = () => { + const checkUserIndexPermission = async () => { try { - const userCanDelete = canDeleteIndex(indexName, toastNotificationService); + const userCanDelete = await canDeleteIndex(indexName, toastNotificationService); if (userCanDelete) { setUserCanDeleteIndex(true); } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx index d93baee97c533..0db4c5d30fbeb 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx @@ -30,7 +30,11 @@ export const DeleteModelsModal: FC = ({ models, onClose .map((model) => model.model_id); return ( - + = ({ models, onClose /> - + { description: i18n.translate('xpack.ml.trainedModels.modelsList.deleteModelActionLabel', { defaultMessage: 'Delete model', }), + 'data-test-subj': 'mlModelsTableRowDeleteAction', icon: 'trash', type: 'icon', color: 'danger', @@ -353,7 +355,7 @@ export const ModelsList: FC = () => { enabled: (item) => { // TODO check for permissions to delete ingest pipelines. // ATM undefined means pipelines fetch failed server-side. - return !item.pipelines; + return !isPopulatedObject(item.pipelines); }, }, ]; @@ -389,6 +391,7 @@ export const ModelsList: FC = () => { iconType={itemIdToExpandedRowMap[item.model_id] ? 'arrowUp' : 'arrowDown'} /> ), + 'data-test-subj': 'mlModelsTableRowDetailsToggle', }, { field: ModelsTableToConfigMapping.id, @@ -397,6 +400,7 @@ export const ModelsList: FC = () => { }), sortable: true, truncateText: true, + 'data-test-subj': 'mlModelsTableColumnId', }, { field: ModelsTableToConfigMapping.description, @@ -406,6 +410,7 @@ export const ModelsList: FC = () => { }), sortable: false, truncateText: true, + 'data-test-subj': 'mlModelsTableColumnDescription', }, { field: ModelsTableToConfigMapping.type, @@ -418,11 +423,14 @@ export const ModelsList: FC = () => { {types.map((type) => ( - {type} + + {type} + ))} ), + 'data-test-subj': 'mlModelsTableColumnType', }, { field: ModelsTableToConfigMapping.createdAt, @@ -432,12 +440,14 @@ export const ModelsList: FC = () => { dataType: 'date', render: timeFormatter, sortable: true, + 'data-test-subj': 'mlModelsTableColumnCreatedAt', }, { name: i18n.translate('xpack.ml.trainedModels.modelsList.actionsHeader', { defaultMessage: 'Actions', }), actions, + 'data-test-subj': 'mlModelsTableColumnActions', }, ]; @@ -492,8 +502,7 @@ export const ModelsList: FC = () => { defaultMessage: 'Select a model', }); } - - if (Array.isArray(item.pipelines) && item.pipelines.length > 0) { + if (isPopulatedObject(item.pipelines)) { return i18n.translate('xpack.ml.trainedModels.modelsList.disableSelectableMessage', { defaultMessage: 'Model has associated pipelines', }); @@ -507,7 +516,7 @@ export const ModelsList: FC = () => { return ''; }, - selectable: (item) => !item.pipelines && !isBuiltInModel(item), + selectable: (item) => !isPopulatedObject(item.pipelines) && !isBuiltInModel(item), onSelectionChange: (selectedItems) => { setSelectedModels(selectedItems); }, @@ -574,6 +583,7 @@ export const ModelsList: FC = () => { pagination={pagination} onTableChange={onTableChange} sorting={sorting} + data-test-subj={isLoading ? 'mlModelsTable loading' : 'mlModelsTable loaded'} />

{modelsToDelete.length > 0 && ( diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_monitoring_time.ts b/x-pack/plugins/monitoring/public/application/hooks/use_monitoring_time.ts index e512f90d76e69..3054714ec3aa6 100644 --- a/x-pack/plugins/monitoring/public/application/hooks/use_monitoring_time.ts +++ b/x-pack/plugins/monitoring/public/application/hooks/use_monitoring_time.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useCallback, useState, useContext } from 'react'; +import { useCallback, useState, useContext, useEffect } from 'react'; import createContainer from 'constate'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { Legacy } from '../../legacy_shims'; @@ -53,6 +53,18 @@ export const useMonitoringTime = () => { [currentTimerange, setTimeRange, state] ); + useEffect(() => { + const sub = Legacy.shims.timefilter.getTimeUpdate$().subscribe(function onTimeUpdate() { + const updatedTime = Legacy.shims.timefilter.getTime(); + setTimeRange({ ...currentTimerange, ...updatedTime }); + state.time = { ...updatedTime }; + state.save?.(); + }); + + return () => sub.unsubscribe(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return { currentTimerange, setTimeRange, diff --git a/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx b/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx index 7a7130007dad6..32bbdd6ecbeda 100644 --- a/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx +++ b/x-pack/plugins/monitoring/public/components/shared/toolbar.tsx @@ -40,14 +40,8 @@ export const MonitoringToolbar: React.FC = ({ pageTitle, return; } handleTimeChange(selectedTime.start, selectedTime.end); - state.time = { - from: selectedTime.start, - to: selectedTime.end, - }; - Legacy.shims.timefilter.setTime(state.time); - state.save?.(); }, - [handleTimeChange, state] + [handleTimeChange] ); const onRefreshChange = useCallback( diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index 2170b50f195b4..a0487421a9a0d 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -220,6 +220,17 @@ export class HeadlessChromiumDriverFactory { }) ); + const uncaughtExceptionPageError$ = Rx.fromEvent(page, 'pageerror').pipe( + map((err) => { + logger.warning( + i18n.translate('xpack.reporting.browsers.chromium.pageErrorDetected', { + defaultMessage: `Reporting encountered an uncaught error on the page that will be ignored: {err}`, + values: { err: err.toString() }, + }) + ); + }) + ); + const pageRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe( map((req) => { const failure = req.failure && req.failure(); @@ -231,7 +242,7 @@ export class HeadlessChromiumDriverFactory { }) ); - return Rx.merge(consoleMessages$, pageRequestFailed$); + return Rx.merge(consoleMessages$, uncaughtExceptionPageError$, pageRequestFailed$); } getProcessLogger(browser: puppeteer.Browser, logger: LevelLogger): Rx.Observable { @@ -266,21 +277,10 @@ export class HeadlessChromiumDriverFactory { }) ); - const uncaughtExceptionPageError$ = Rx.fromEvent(page, 'pageerror').pipe( - mergeMap((err) => { - return Rx.throwError( - i18n.translate('xpack.reporting.browsers.chromium.pageErrorDetected', { - defaultMessage: `Reporting encountered an error on the page: {err}`, - values: { err: err.toString() }, - }) - ); - }) - ); - const browserDisconnect$ = Rx.fromEvent(browser, 'disconnected').pipe( mergeMap(() => Rx.throwError(getChromiumDisconnectedError())) ); - return Rx.merge(pageError$, uncaughtExceptionPageError$, browserDisconnect$); + return Rx.merge(pageError$, browserDisconnect$); } } diff --git a/x-pack/plugins/rule_registry/server/scripts/get_alerts_index.sh b/x-pack/plugins/rule_registry/server/scripts/get_alerts_index.sh index bfa74aa016f02..addd2d4ab6195 100755 --- a/x-pack/plugins/rule_registry/server/scripts/get_alerts_index.sh +++ b/x-pack/plugins/rule_registry/server/scripts/get_alerts_index.sh @@ -20,4 +20,4 @@ curl -v -k \ -u $USER:changeme \ -X GET "${KIBANA_URL}${SPACE_URL}/internal/rac/alerts/index" | jq . -# -X GET "${KIBANA_URL}${SPACE_URL}/api/apm/settings/apm-alerts-as-data-indices" | jq . +# -X GET "${KIBANA_URL}${SPACE_URL}/internal/apm/settings/apm-alerts-as-data-indices" | jq . diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 52c82f57d9ec3..d2120faf09dfb 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -334,7 +334,7 @@ export const ELASTIC_NAME = 'estc'; export const METADATA_TRANSFORM_STATS_URL = `/api/transform/transforms/${METADATA_TRANSFORMS_PATTERN}/_stats`; -export const HOST_RISK_SCORES_INDEX = 'ml_host_risk_score_latest'; +export const RISKY_HOSTS_INDEX_PREFIX = 'ml_host_risk_score_latest_'; export const TRANSFORM_STATES = { ABORTING: 'aborting', diff --git a/x-pack/plugins/security_solution/cypress/integration/overview/risky_hosts_panel.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview/risky_hosts_panel.spec.ts index df57f7cc8d050..1c55a38b32495 100644 --- a/x-pack/plugins/security_solution/cypress/integration/overview/risky_hosts_panel.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/overview/risky_hosts_panel.spec.ts @@ -17,8 +17,12 @@ import { import { loginAndWaitForPage } from '../../tasks/login'; import { OVERVIEW_URL } from '../../urls/navigation'; import { cleanKibana } from '../../tasks/common'; +import { changeSpace } from '../../tasks/kibana_navigation'; +import { createSpace, removeSpace } from '../../tasks/api_calls/spaces'; import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; +const testSpaceName = 'test'; + describe('Risky Hosts Link Panel', () => { before(() => { cleanKibana(); @@ -40,10 +44,12 @@ describe('Risky Hosts Link Panel', () => { describe('enabled module', () => { before(() => { esArchiverLoad('risky_hosts'); + createSpace(testSpaceName); }); after(() => { esArchiverUnload('risky_hosts'); + removeSpace(testSpaceName); }); it('renders disabled dashboard module as expected when there are no hosts in the selected time period', () => { @@ -57,13 +63,19 @@ describe('Risky Hosts Link Panel', () => { cy.get(`${OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 0 hosts'); }); - it('renders dashboard module as expected when there are hosts in the selected time period', () => { + it('renders space aware dashboard module as expected when there are hosts in the selected time period', () => { loginAndWaitForPage(OVERVIEW_URL); cy.get( `${OVERVIEW_RISKY_HOSTS_LINKS} ${OVERVIEW_RISKY_HOSTS_LINKS_WARNING_INNER_PANEL}` ).should('not.exist'); cy.get(`${OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON}`).should('be.disabled'); cy.get(`${OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 1 host'); + + changeSpace(testSpaceName); + cy.visit(`/s/${testSpaceName}${OVERVIEW_URL}`); + cy.get(`${OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON}`).should('be.disabled'); + cy.get(`${OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 0 hosts'); + cy.get(`${OVERVIEW_RISKY_HOSTS_ENABLE_MODULE_BUTTON}`).should('exist'); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts b/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts index 36b870598eff4..c20f4bd054a7c 100644 --- a/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts +++ b/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts @@ -25,3 +25,7 @@ export const OVERVIEW_PAGE = export const TIMELINES_PAGE = '[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Timelines"]'; + +export const SPACES_BUTTON = '[data-test-subj="spacesNavSelector"]'; + +export const getGoToSpaceMenuItem = (space: string) => `[data-test-subj="${space}-gotoSpace"]`; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/spaces.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/spaces.ts new file mode 100644 index 0000000000000..cd12fab70a891 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/spaces.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const createSpace = (id: string) => { + cy.request({ + method: 'POST', + url: 'api/spaces/space', + body: { + id, + name: id, + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); +}; + +export const removeSpace = (id: string) => { + cy.request(`/api/spaces/space/${id}`); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/kibana_navigation.ts b/x-pack/plugins/security_solution/cypress/tasks/kibana_navigation.ts index 3b3fc0c6da4e4..43630e63ebfe2 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/kibana_navigation.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/kibana_navigation.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { KIBANA_NAVIGATION_TOGGLE } from '../screens/kibana_navigation'; +import { + KIBANA_NAVIGATION_TOGGLE, + SPACES_BUTTON, + getGoToSpaceMenuItem, +} from '../screens/kibana_navigation'; export const navigateFromKibanaCollapsibleTo = (page: string) => { cy.get(page).click(); @@ -14,3 +18,9 @@ export const navigateFromKibanaCollapsibleTo = (page: string) => { export const openKibanaNavigation = () => { cy.get(KIBANA_NAVIGATION_TOGGLE).click(); }; + +export const changeSpace = (space: string) => { + cy.get(`${SPACES_BUTTON}`).click(); + cy.get(getGoToSpaceMenuItem(space)).click(); + cy.get(`[data-test-subj="space-avatar-${space}"]`).should('exist'); +}; diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index 8bb1f4d75e6bc..a76b942e555bc 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -12,15 +12,16 @@ "actions", "alerting", "cases", - "ruleRegistry", "data", "dataEnhanced", "embeddable", + "eventLog", "features", - "taskManager", "inspector", "licensing", "maps", + "ruleRegistry", + "taskManager", "timelines", "triggersActionsUi", "uiActions" diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx index ad5a85a03464e..6b05ee6403db3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx @@ -11,9 +11,12 @@ import { EuiText, EuiHealth, EuiToolTip, + EuiIcon, + EuiLink, } from '@elastic/eui'; -import { FormattedRelative } from '@kbn/i18n/react'; +import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; import * as H from 'history'; +import { sum } from 'lodash'; import React, { Dispatch } from 'react'; import { isMlRule } from '../../../../../../common/machine_learning/helpers'; @@ -36,10 +39,11 @@ import { RulesTableAction } from '../../../../containers/detection_engine/rules/ import { LocalizedDateTooltip } from '../../../../../common/components/localized_date_tooltip'; import { LinkAnchor } from '../../../../../common/components/links'; import { getToolTipContent, canEditRuleWithActions } from '../../../../../common/utils/privileges'; +import { PopoverTooltip } from './popover_tooltip'; import { TagsDisplay } from './tag_display'; import { getRuleStatusText } from '../../../../../../common/detection_engine/utils'; import { APP_ID, SecurityPageName } from '../../../../../../common/constants'; -import { NavigateToAppOptions } from '../../../../../../../../../src/core/public'; +import { DocLinksStart, NavigateToAppOptions } from '../../../../../../../../../src/core/public'; export const getActions = ( dispatch: React.Dispatch, @@ -312,7 +316,8 @@ export const getColumns = ({ export const getMonitoringColumns = ( navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise, - formatUrl: FormatUrl + formatUrl: FormatUrl, + docLinks: DocLinksStart ): RulesStatusesColumns[] => { const cols: RulesStatusesColumns[] = [ { @@ -344,12 +349,17 @@ export const getMonitoringColumns = ( }, { field: 'current_status.bulk_create_time_durations', - name: i18n.COLUMN_INDEXING_TIMES, + name: ( + <> + {i18n.COLUMN_INDEXING_TIMES}{' '} + + + + + ), render: (value: RuleStatus['current_status']['bulk_create_time_durations']) => ( - {value != null && value.length > 0 - ? Math.max(...value?.map((item) => Number.parseFloat(item))) - : getEmptyTagValue()} + {value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()} ), truncateText: true, @@ -357,12 +367,17 @@ export const getMonitoringColumns = ( }, { field: 'current_status.search_after_time_durations', - name: i18n.COLUMN_QUERY_TIMES, + name: ( + <> + {i18n.COLUMN_QUERY_TIMES}{' '} + + + + + ), render: (value: RuleStatus['current_status']['search_after_time_durations']) => ( - {value != null && value.length > 0 - ? Math.max(...value?.map((item) => Number.parseFloat(item))) - : getEmptyTagValue()} + {value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()} ), truncateText: true, @@ -370,7 +385,28 @@ export const getMonitoringColumns = ( }, { field: 'current_status.gap', - name: i18n.COLUMN_GAP, + name: ( + <> + {i18n.COLUMN_GAP} + + +

+ + {'see documentation'} + + ), + }} + /> +

+
+
+ + ), render: (value: RuleStatus['current_status']['gap']) => ( {value ?? getEmptyTagValue()} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/popover_tooltip.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/popover_tooltip.tsx new file mode 100644 index 0000000000000..5cf0a0c0b28fc --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/popover_tooltip.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { EuiPopover, EuiButtonIcon } from '@elastic/eui'; +import * as i18n from '../translations'; + +interface PopoverTooltipProps { + columnName: string; + children: React.ReactNode; +} + +/** + * Table column tooltip component utilizing EuiPopover for rich content like documentation links + * @param columnName string Name of column to use as aria-label of button + * @param children React.ReactNode of content to display in popover tooltip + */ +const PopoverTooltipComponent = ({ columnName, children }: PopoverTooltipProps) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + return ( + setIsPopoverOpen(false)} + button={ + setIsPopoverOpen(!isPopoverOpen)} + size="s" + color="primary" + iconType="questionInCircle" + /> + } + > + {children} + + ); +}; + +export const PopoverTooltip = React.memo(PopoverTooltipComponent); + +PopoverTooltip.displayName = 'PopoverTooltip'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx index f32321a0a03dc..9d9425cdabe63 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx @@ -96,6 +96,7 @@ export const RulesTables = React.memo( setRefreshRulesData, selectedTab, }) => { + const docLinks = useKibana().services.docLinks; const [initLoading, setInitLoading] = useState(true); const { @@ -299,8 +300,8 @@ export const RulesTables = React.memo( ]); const monitoringColumns = useMemo( - () => getMonitoringColumns(navigateToApp, formatUrl), - [navigateToApp, formatUrl] + () => getMonitoringColumns(navigateToApp, formatUrl, docLinks), + [navigateToApp, formatUrl, docLinks] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 28ed14774acf6..53efe28cba49c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -13,6 +13,11 @@ export const BACK_TO_DETECTIONS = i18n.translate( defaultMessage: 'Back to detections', } ); +export const POPOVER_TOOLTIP_ARIA_LABEL = (columnName: string) => + i18n.translate('xpack.securitySolution.detectionEngine.rules.popoverTooltip.ariaLabel', { + defaultMessage: 'Tooltip for column: {columnName}', + values: { columnName }, + }); export const IMPORT_RULE = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.importRuleTitle', @@ -364,6 +369,13 @@ export const COLUMN_INDEXING_TIMES = i18n.translate( } ); +export const COLUMN_INDEXING_TIMES_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.indexingTimesTooltip', + { + defaultMessage: 'Total time spent indexing alerts during last Rule execution', + } +); + export const COLUMN_QUERY_TIMES = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.columns.queryTimes', { @@ -371,6 +383,13 @@ export const COLUMN_QUERY_TIMES = i18n.translate( } ); +export const COLUMN_QUERY_TIMES_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.queryTimesTooltip', + { + defaultMessage: 'Total time spent querying source indices during last Rule execution', + } +); + export const COLUMN_GAP = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.columns.gap', { diff --git a/x-pack/plugins/security_solution/public/helpers.test.ts b/x-pack/plugins/security_solution/public/helpers.test.ts index eaaf518d486ad..f2e37cd995a4f 100644 --- a/x-pack/plugins/security_solution/public/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/helpers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { parseRoute } from './helpers'; +import { parseRoute, getHostRiskIndex } from './helpers'; describe('public helpers parseRoute', () => { it('should properly parse hash route', () => { @@ -54,3 +54,9 @@ describe('public helpers parseRoute', () => { }); }); }); + +describe('public helpers export getHostRiskIndex', () => { + it('should properly return index if space is specified', () => { + expect(getHostRiskIndex('testName')).toEqual('ml_host_risk_score_latest_testName'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/helpers.ts b/x-pack/plugins/security_solution/public/helpers.ts index 9d842e0c0a128..aba46cffee193 100644 --- a/x-pack/plugins/security_solution/public/helpers.ts +++ b/x-pack/plugins/security_solution/public/helpers.ts @@ -9,7 +9,14 @@ import { isEmpty } from 'lodash/fp'; import { matchPath } from 'react-router-dom'; import { CoreStart } from '../../../../src/core/public'; -import { ALERTS_PATH, APP_ID, EXCEPTIONS_PATH, RULES_PATH, UEBA_PATH } from '../common/constants'; +import { + ALERTS_PATH, + APP_ID, + EXCEPTIONS_PATH, + RULES_PATH, + UEBA_PATH, + RISKY_HOSTS_INDEX_PREFIX, +} from '../common/constants'; import { FactoryQueryTypes, StrategyResponseType, @@ -147,3 +154,7 @@ export const isDetectionsPath = (pathname: string): boolean => { strict: false, }); }; + +export const getHostRiskIndex = (spaceId: string): string => { + return `${RISKY_HOSTS_INDEX_PREFIX}${spaceId}`; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts index 79ca595fbb61b..8af353a3c9531 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts @@ -6,6 +6,7 @@ */ import { + CreateExceptionListItemSchema, ExceptionListItemSchema, FoundExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; @@ -65,6 +66,19 @@ export async function getHostIsolationExceptionItems({ return entries; } +export async function createHostIsolationExceptionItem({ + http, + exception, +}: { + http: HttpStart; + exception: CreateExceptionListItemSchema; +}): Promise { + await ensureHostIsolationExceptionsListExists(http); + return http.post(EXCEPTION_LIST_ITEM_URL, { + body: JSON.stringify(exception), + }); +} + export async function deleteHostIsolationExceptionItems(http: HttpStart, id: string) { await ensureHostIsolationExceptionsListExists(http); return http.delete(EXCEPTION_LIST_ITEM_URL, { diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts index 0a9f776655371..a5fae36486f98 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts @@ -14,6 +14,20 @@ export type HostIsolationExceptionsPageDataChanged = payload: HostIsolationExceptionsPageState['entries']; }; +export type HostIsolationExceptionsFormStateChanged = + Action<'hostIsolationExceptionsFormStateChanged'> & { + payload: HostIsolationExceptionsPageState['form']['status']; + }; + +export type HostIsolationExceptionsFormEntryChanged = + Action<'hostIsolationExceptionsFormEntryChanged'> & { + payload: HostIsolationExceptionsPageState['form']['entry']; + }; + +export type HostIsolationExceptionsCreateEntry = Action<'hostIsolationExceptionsCreateEntry'> & { + payload: HostIsolationExceptionsPageState['form']['entry']; +}; + export type HostIsolationExceptionsDeleteItem = Action<'hostIsolationExceptionsMarkToDelete'> & { payload?: ExceptionListItemSchema; }; @@ -24,9 +38,10 @@ export type HostIsolationExceptionsDeleteStatusChanged = Action<'hostIsolationExceptionsDeleteStatusChanged'> & { payload: HostIsolationExceptionsPageState['deletion']['status']; }; - export type HostIsolationExceptionsPageAction = | HostIsolationExceptionsPageDataChanged + | HostIsolationExceptionsCreateEntry + | HostIsolationExceptionsFormStateChanged | HostIsolationExceptionsDeleteItem | HostIsolationExceptionsSubmitDelete | HostIsolationExceptionsDeleteStatusChanged; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/builders.ts index 68a50f9c813f4..8f32d9cf8d456 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/builders.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/builders.ts @@ -16,6 +16,10 @@ export const initialHostIsolationExceptionsPageState = (): HostIsolationExceptio page_size: MANAGEMENT_DEFAULT_PAGE_SIZE, filter: '', }, + form: { + entry: undefined, + status: createUninitialisedResourceState(), + }, deletion: { item: undefined, status: createUninitialisedResourceState(), diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts index 984794e074ebb..266853fdab5e2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { applyMiddleware, createStore, Store } from 'redux'; -import { HOST_ISOLATION_EXCEPTIONS_PATH } from '../../../../../common/constants'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { getFoundExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock'; +import { HOST_ISOLATION_EXCEPTIONS_PATH } from '../../../../../common/constants'; import { AppAction } from '../../../../common/store/actions'; import { createSpyMiddleware, @@ -19,8 +20,13 @@ import { isLoadedResourceState, isLoadingResourceState, } from '../../../state'; -import { getHostIsolationExceptionItems, deleteHostIsolationExceptionItems } from '../service'; +import { + createHostIsolationExceptionItem, + deleteHostIsolationExceptionItems, + getHostIsolationExceptionItems, +} from '../service'; import { HostIsolationExceptionsPageState } from '../types'; +import { createEmptyHostIsolationException } from '../utils'; import { initialHostIsolationExceptionsPageState } from './builders'; import { createHostIsolationExceptionsPageMiddleware } from './middleware'; import { hostIsolationExceptionsPageReducer } from './reducer'; @@ -29,6 +35,7 @@ import { getListFetchError } from './selector'; jest.mock('../service'); const getHostIsolationExceptionItemsMock = getHostIsolationExceptionItems as jest.Mock; const deleteHostIsolationExceptionItemsMock = deleteHostIsolationExceptionItems as jest.Mock; +const createHostIsolationExceptionItemMock = createHostIsolationExceptionItem as jest.Mock; const fakeCoreStart = coreMock.createStart({ basePath: '/mock' }); @@ -81,7 +88,7 @@ describe('Host isolation exceptions middleware', () => { }; beforeEach(() => { - getHostIsolationExceptionItemsMock.mockClear(); + getHostIsolationExceptionItemsMock.mockReset(); getHostIsolationExceptionItemsMock.mockImplementation(getFoundExceptionListItemSchemaMock); }); @@ -145,11 +152,74 @@ describe('Host isolation exceptions middleware', () => { }); }); + describe('When adding an item to host isolation exceptions', () => { + let entry: CreateExceptionListItemSchema; + beforeEach(() => { + createHostIsolationExceptionItemMock.mockReset(); + entry = { + ...createEmptyHostIsolationException(), + name: 'test name', + description: 'description', + entries: [ + { + field: 'destination.ip', + operator: 'included', + type: 'match', + value: '10.0.0.1', + }, + ], + }; + }); + it('should dispatch a form loading state when an entry is submited', async () => { + const waiter = spyMiddleware.waitForAction('hostIsolationExceptionsFormStateChanged', { + validate({ payload }) { + return isLoadingResourceState(payload); + }, + }); + store.dispatch({ + type: 'hostIsolationExceptionsCreateEntry', + payload: entry, + }); + await waiter; + }); + it('should dispatch a form success state when an entry is confirmed by the API', async () => { + const waiter = spyMiddleware.waitForAction('hostIsolationExceptionsFormStateChanged', { + validate({ payload }) { + return isLoadedResourceState(payload); + }, + }); + store.dispatch({ + type: 'hostIsolationExceptionsCreateEntry', + payload: entry, + }); + await waiter; + expect(createHostIsolationExceptionItemMock).toHaveBeenCalledWith({ + http: fakeCoreStart.http, + exception: entry, + }); + }); + it('should dispatch a form failure state when an entry is rejected by the API', async () => { + createHostIsolationExceptionItemMock.mockRejectedValue({ + body: { message: 'error message', statusCode: 500, error: 'Not today' }, + }); + const waiter = spyMiddleware.waitForAction('hostIsolationExceptionsFormStateChanged', { + validate({ payload }) { + return isFailedResourceState(payload); + }, + }); + store.dispatch({ + type: 'hostIsolationExceptionsCreateEntry', + payload: entry, + }); + await waiter; + }); + }); + describe('When deleting an item from host isolation exceptions', () => { beforeEach(() => { - deleteHostIsolationExceptionItemsMock.mockClear(); + deleteHostIsolationExceptionItemsMock.mockReset(); deleteHostIsolationExceptionItemsMock.mockReturnValue(undefined); - getHostIsolationExceptionItemsMock.mockClear(); + getHostIsolationExceptionItemsMock.mockReset(); getHostIsolationExceptionItemsMock.mockImplementation(getFoundExceptionListItemSchemaMock); store.dispatch({ type: 'hostIsolationExceptionsMarkToDelete', diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts index 4946cac488700..bbc754e8155b0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts @@ -6,11 +6,13 @@ */ import { + CreateExceptionListItemSchema, ExceptionListItemSchema, FoundExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { CoreStart, HttpSetup, HttpStart } from 'kibana/public'; import { matchPath } from 'react-router-dom'; +import { transformNewItemOutput } from '@kbn/securitysolution-list-hooks'; import { AppLocation, Immutable } from '../../../../../common/endpoint/types'; import { ImmutableMiddleware, ImmutableMiddlewareAPI } from '../../../../common/store'; import { AppAction } from '../../../../common/store/actions'; @@ -20,7 +22,11 @@ import { createFailedResourceState, createLoadedResourceState, } from '../../../state/async_resource_builders'; -import { deleteHostIsolationExceptionItems, getHostIsolationExceptionItems } from '../service'; +import { + deleteHostIsolationExceptionItems, + getHostIsolationExceptionItems, + createHostIsolationExceptionItem, +} from '../service'; import { HostIsolationExceptionsPageState } from '../types'; import { getCurrentListPageDataState, getCurrentLocation, getItemToDelete } from './selector'; @@ -39,12 +45,50 @@ export const createHostIsolationExceptionsPageMiddleware = ( if (action.type === 'userChangedUrl' && isHostIsolationExceptionsPage(action.payload)) { loadHostIsolationExceptionsList(store, coreStart.http); } + + if (action.type === 'hostIsolationExceptionsCreateEntry') { + createHostIsolationException(store, coreStart.http); + } + if (action.type === 'hostIsolationExceptionsSubmitDelete') { deleteHostIsolationExceptionsItem(store, coreStart.http); } }; }; +async function createHostIsolationException( + store: ImmutableMiddlewareAPI, + http: HttpStart +) { + const { dispatch } = store; + const entry = transformNewItemOutput( + store.getState().form.entry as CreateExceptionListItemSchema + ); + dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: { + type: 'LoadingResourceState', + // @ts-expect-error-next-line will be fixed with when AsyncResourceState is refactored (#830) + previousState: entry, + }, + }); + try { + const response = await createHostIsolationExceptionItem({ + http, + exception: entry, + }); + dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: createLoadedResourceState(response), + }); + } catch (error) { + dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: createFailedResourceState(error.body ?? error), + }); + } +} + async function loadHostIsolationExceptionsList( store: ImmutableMiddlewareAPI, http: HttpStart diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.test.ts index 211b03f36d965..98b459fac41d3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.test.ts @@ -11,6 +11,7 @@ import { initialHostIsolationExceptionsPageState } from './builders'; import { HOST_ISOLATION_EXCEPTIONS_PATH } from '../../../../../common/constants'; import { hostIsolationExceptionsPageReducer } from './reducer'; import { getCurrentLocation } from './selector'; +import { createEmptyHostIsolationException } from '../utils'; describe('Host Isolation Exceptions Reducer', () => { let initialState: HostIsolationExceptionsPageState; @@ -41,4 +42,13 @@ describe('Host Isolation Exceptions Reducer', () => { }); }); }); + it('should set an initial loading state when creating new entries', () => { + const entry = createEmptyHostIsolationException(); + const result = hostIsolationExceptionsPageReducer(initialState, { + type: 'hostIsolationExceptionsCreateEntry', + payload: entry, + }); + expect(result.form.status).toEqual({ type: 'UninitialisedResourceState' }); + expect(result.form.entry).toBe(entry); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts index 09182661a80b3..d97295598f445 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts @@ -38,6 +38,24 @@ export const hostIsolationExceptionsPageReducer: StateReducer = ( action ) => { switch (action.type) { + case 'hostIsolationExceptionsCreateEntry': { + return { + ...state, + form: { + entry: action.payload, + status: createUninitialisedResourceState(), + }, + }; + } + case 'hostIsolationExceptionsFormStateChanged': { + return { + ...state, + form: { + ...state.form, + status: action.payload, + }, + }; + } case 'hostIsolationExceptionsPageDataChanged': { return { ...state, diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/types.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/types.ts index 443a86fefab83..1a74042fb652e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/types.ts @@ -6,6 +6,7 @@ */ import type { + CreateExceptionListItemSchema, ExceptionListItemSchema, FoundExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; @@ -27,4 +28,8 @@ export interface HostIsolationExceptionsPageState { item?: ExceptionListItemSchema; status: AsyncResourceState; }; + form: { + entry?: CreateExceptionListItemSchema; + status: AsyncResourceState; + }; } diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/utils.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/utils.ts new file mode 100644 index 0000000000000..bfb1ac048e286 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/utils.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID } from '@kbn/securitysolution-list-constants'; +import ipaddr from 'ipaddr.js'; + +export function createEmptyHostIsolationException(): CreateExceptionListItemSchema { + return { + comments: [], + description: '', + entries: [ + { + field: 'destination.ip', + operator: 'included', + type: 'match', + value: '', + }, + ], + item_id: undefined, + list_id: ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID, + name: '', + namespace_type: 'agnostic', + os_types: ['windows', 'linux', 'macos'], + tags: ['policy:all'], + type: 'simple', + }; +} + +export function isValidIPv4OrCIDR(maybeIp: string): boolean { + try { + ipaddr.IPv4.parseCIDR(maybeIp); + return true; + } catch (e) { + return ipaddr.IPv4.isValid(maybeIp); + } +} diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/empty.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/empty.tsx index d7c512794173c..eb53268a9fbd8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/empty.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/empty.tsx @@ -7,7 +7,7 @@ import React, { memo } from 'react'; import styled, { css } from 'styled-components'; -import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; const EmptyPrompt = styled(EuiEmptyPrompt)` @@ -16,7 +16,7 @@ const EmptyPrompt = styled(EuiEmptyPrompt)` `} `; -export const HostIsolationExceptionsEmptyState = memo<{}>(() => { +export const HostIsolationExceptionsEmptyState = memo<{ onAdd: () => void }>(({ onAdd }) => { return ( (() => { defaultMessage="There are currently no host isolation exceptions" /> } + actions={ + + + + } /> ); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx new file mode 100644 index 0000000000000..b06449de69d8c --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createEmptyHostIsolationException } from '../../utils'; +import { HostIsolationExceptionsForm } from './form'; +import React from 'react'; +import { + AppContextTestRender, + createAppRootMockRenderer, +} from '../../../../../common/mock/endpoint'; +import { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import userEvent from '@testing-library/user-event'; + +describe('When on the host isolation exceptions add entry form', () => { + let render: ( + exception: CreateExceptionListItemSchema + ) => ReturnType; + let renderResult: ReturnType; + const onChange = jest.fn(); + const onError = jest.fn(); + + beforeEach(() => { + onChange.mockReset(); + onError.mockReset(); + const mockedContext = createAppRootMockRenderer(); + render = (exception: CreateExceptionListItemSchema) => { + return mockedContext.render( + + ); + }; + }); + + describe('When creating a new exception', () => { + let newException: CreateExceptionListItemSchema; + beforeEach(() => { + newException = createEmptyHostIsolationException(); + renderResult = render(newException); + }); + it('should render the form with empty inputs', () => { + expect(renderResult.getByTestId('hostIsolationExceptions-form-name-input')).toHaveValue(''); + expect(renderResult.getByTestId('hostIsolationExceptions-form-ip-input')).toHaveValue(''); + expect( + renderResult.getByTestId('hostIsolationExceptions-form-description-input') + ).toHaveValue(''); + }); + it('should call onError with true when a wrong ip value is introduced', () => { + const ipInput = renderResult.getByTestId('hostIsolationExceptions-form-ip-input'); + userEvent.type(ipInput, 'not an ip'); + expect(onError).toHaveBeenCalledWith(true); + }); + it('should call onError with false when a correct values are introduced', () => { + const ipInput = renderResult.getByTestId('hostIsolationExceptions-form-ip-input'); + const nameInput = renderResult.getByTestId('hostIsolationExceptions-form-name-input'); + + userEvent.type(nameInput, 'test name'); + userEvent.type(ipInput, '10.0.0.1'); + + expect(onError).toHaveBeenLastCalledWith(false); + }); + it('should call onChange when a value is introduced in a field', () => { + const ipInput = renderResult.getByTestId('hostIsolationExceptions-form-ip-input'); + userEvent.type(ipInput, '10.0.0.1'); + expect(onChange).toHaveBeenLastCalledWith({ + ...newException, + entries: [ + { field: 'destination.ip', operator: 'included', type: 'match', value: '10.0.0.1' }, + ], + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx new file mode 100644 index 0000000000000..84263f9d07c81 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx @@ -0,0 +1,206 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiFieldText, + EuiForm, + EuiFormRow, + EuiHorizontalRule, + EuiSpacer, + EuiText, + EuiTextArea, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { isValidIPv4OrCIDR } from '../../utils'; +import { + DESCRIPTION_LABEL, + DESCRIPTION_PLACEHOLDER, + IP_ERROR, + IP_LABEL, + IP_PLACEHOLDER, + NAME_ERROR, + NAME_LABEL, + NAME_PLACEHOLDER, +} from './translations'; + +interface ExceptionIpEntry { + field: 'destination.ip'; + operator: 'included'; + type: 'match'; + value: ''; +} + +export const HostIsolationExceptionsForm: React.FC<{ + exception: CreateExceptionListItemSchema; + onError: (error: boolean) => void; + onChange: (exception: CreateExceptionListItemSchema) => void; +}> = memo(({ exception, onError, onChange }) => { + const [hasBeenInputNameVisited, setHasBeenInputNameVisited] = useState(false); + const [hasBeenInputIpVisited, setHasBeenInputIpVisited] = useState(false); + const [hasNameError, setHasNameError] = useState(true); + const [hasIpError, setHasIpError] = useState(true); + + useEffect(() => { + onError(hasNameError || hasIpError); + }, [hasNameError, hasIpError, onError]); + + const handleOnChangeName = useCallback( + (event: React.ChangeEvent) => { + const name = event.target.value; + if (!name.trim()) { + setHasNameError(true); + return; + } + setHasNameError(false); + onChange({ ...exception, name }); + }, + [exception, onChange] + ); + + const handleOnIpChange = useCallback( + (event: React.ChangeEvent) => { + const ip = event.target.value; + if (!isValidIPv4OrCIDR(ip)) { + setHasIpError(true); + return; + } + setHasIpError(false); + onChange({ + ...exception, + entries: [ + { + field: 'destination.ip', + operator: 'included', + type: 'match', + value: ip, + }, + ], + }); + }, + [exception, onChange] + ); + + const handleOnDescriptionChange = useCallback( + (event: React.ChangeEvent) => { + onChange({ ...exception, description: event.target.value }); + }, + [exception, onChange] + ); + + const nameInput = useMemo( + () => ( + + !hasBeenInputNameVisited && setHasBeenInputNameVisited(true)} + /> + + ), + [hasNameError, hasBeenInputNameVisited, exception.name, handleOnChangeName] + ); + + const ipInput = useMemo( + () => ( + + !hasBeenInputIpVisited && setHasBeenInputIpVisited(true)} + /> + + ), + [hasIpError, hasBeenInputIpVisited, exception.entries, handleOnIpChange] + ); + + const descriptionInput = useMemo( + () => ( + + + + ), + [exception.description, handleOnDescriptionChange] + ); + + return ( + + +

+ +

+
+ + + + + {nameInput} + {descriptionInput} + + + +

+ +

+
+ + + + + {ipInput} +
+ ); +}); + +HostIsolationExceptionsForm.displayName = 'HostIsolationExceptionsForm'; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.test.tsx new file mode 100644 index 0000000000000..6cfc9f56beadf --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.test.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + AppContextTestRender, + createAppRootMockRenderer, +} from '../../../../../common/mock/endpoint'; +import userEvent from '@testing-library/user-event'; +import { HostIsolationExceptionsFormFlyout } from './form_flyout'; +import { act } from 'react-dom/test-utils'; +import { HOST_ISOLATION_EXCEPTIONS_PATH } from '../../../../../../common/constants'; + +jest.mock('../../service.ts'); + +describe('When on the host isolation exceptions flyout form', () => { + let mockedContext: AppContextTestRender; + let render: () => ReturnType; + let renderResult: ReturnType; + let waitForAction: AppContextTestRender['middlewareSpy']['waitForAction']; + + // const createHostIsolationExceptionItemMock = createHostIsolationExceptionItem as jest.mock; + + beforeEach(() => { + mockedContext = createAppRootMockRenderer(); + render = () => { + return mockedContext.render(); + }; + waitForAction = mockedContext.middlewareSpy.waitForAction; + }); + + describe('When creating a new exception', () => { + describe('with invalid data', () => { + it('should show disabled buttons when the form first load', () => { + renderResult = render(); + expect(renderResult.getByTestId('add-exception-cancel-button')).not.toHaveAttribute( + 'disabled' + ); + expect(renderResult.getByTestId('add-exception-confirm-button')).toHaveAttribute( + 'disabled' + ); + }); + }); + describe('with valid data', () => { + beforeEach(() => { + renderResult = render(); + const ipInput = renderResult.getByTestId('hostIsolationExceptions-form-ip-input'); + const nameInput = renderResult.getByTestId('hostIsolationExceptions-form-name-input'); + userEvent.type(nameInput, 'test name'); + userEvent.type(ipInput, '10.0.0.1'); + }); + it('should show enable buttons when the form is valid', () => { + expect(renderResult.getByTestId('add-exception-cancel-button')).not.toHaveAttribute( + 'disabled' + ); + expect(renderResult.getByTestId('add-exception-confirm-button')).not.toHaveAttribute( + 'disabled' + ); + }); + it('should submit the entry data when submit is pressed with valid data', async () => { + const confirmButton = renderResult.getByTestId('add-exception-confirm-button'); + expect(confirmButton).not.toHaveAttribute('disabled'); + const waiter = waitForAction('hostIsolationExceptionsCreateEntry'); + userEvent.click(confirmButton); + await waiter; + }); + it('should disable the submit button when an operation is in progress', () => { + act(() => { + mockedContext.store.dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }); + }); + const confirmButton = renderResult.getByTestId('add-exception-confirm-button'); + expect(confirmButton).toHaveAttribute('disabled'); + }); + it('should show a toast and close the flyout when the operation is finished', () => { + mockedContext.history.push(`${HOST_ISOLATION_EXCEPTIONS_PATH}?show=create`); + act(() => { + mockedContext.store.dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: { + type: 'LoadedResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }); + }); + expect(mockedContext.coreStart.notifications.toasts.addSuccess).toHaveBeenCalled(); + expect(mockedContext.history.location.search).toBe(''); + }); + it('should show an error toast operation fails and enable the submit button', () => { + act(() => { + mockedContext.store.dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: { + type: 'FailedResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }); + }); + expect(mockedContext.coreStart.notifications.toasts.addDanger).toHaveBeenCalled(); + const confirmButton = renderResult.getByTestId('add-exception-confirm-button'); + expect(confirmButton).not.toHaveAttribute('disabled'); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx new file mode 100644 index 0000000000000..5502a1b8ea2b1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx @@ -0,0 +1,180 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import { Loader } from '../../../../../common/components/loader'; +import { useToasts } from '../../../../../common/lib/kibana'; +import { + isFailedResourceState, + isLoadedResourceState, + isLoadingResourceState, +} from '../../../../state/async_resource_state'; +import { HostIsolationExceptionsPageAction } from '../../store/action'; +import { createEmptyHostIsolationException } from '../../utils'; +import { + useHostIsolationExceptionsNavigateCallback, + useHostIsolationExceptionsSelector, +} from '../hooks'; +import { HostIsolationExceptionsForm } from './form'; + +export const HostIsolationExceptionsFormFlyout: React.FC<{}> = memo(() => { + const dispatch = useDispatch>(); + const toasts = useToasts(); + + const creationInProgress = useHostIsolationExceptionsSelector((state) => + isLoadingResourceState(state.form.status) + ); + const creationSuccessful = useHostIsolationExceptionsSelector((state) => + isLoadedResourceState(state.form.status) + ); + const creationFailure = useHostIsolationExceptionsSelector((state) => + isFailedResourceState(state.form.status) + ); + + const navigateCallback = useHostIsolationExceptionsNavigateCallback(); + + const [formHasError, setFormHasError] = useState(true); + const [exception, setException] = useState(undefined); + + const onCancel = useCallback( + () => + navigateCallback({ + show: undefined, + id: undefined, + }), + [navigateCallback] + ); + + useEffect(() => { + setException(createEmptyHostIsolationException()); + }, []); + + useEffect(() => { + if (creationSuccessful) { + onCancel(); + dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: { + type: 'UninitialisedResourceState', + }, + }); + toasts.addSuccess( + i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.creationSuccessToastTitle', + { + defaultMessage: '"{name}" has been added to the host isolation exceptions list.', + values: { name: exception?.name }, + } + ) + ); + } + }, [creationSuccessful, onCancel, dispatch, toasts, exception?.name]); + + useEffect(() => { + if (creationFailure) { + toasts.addDanger( + i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.creationFailureToastTitle', + { + defaultMessage: 'There was an error creating the exception', + } + ) + ); + } + }, [dispatch, toasts, creationFailure]); + + const handleOnCancel = useCallback(() => { + if (creationInProgress) return; + onCancel(); + }, [creationInProgress, onCancel]); + + const handleOnSubmit = useCallback(() => { + dispatch({ + type: 'hostIsolationExceptionsCreateEntry', + payload: exception, + }); + }, [dispatch, exception]); + + const confirmButtonMemo = useMemo( + () => ( + + + + ), + [formHasError, creationInProgress, handleOnSubmit] + ); + + return exception ? ( + + + +

+ +

+
+
+ + + + + + + + + + + + + {confirmButtonMemo} + + +
+ ) : ( + + ); +}); + +HostIsolationExceptionsFormFlyout.displayName = 'HostIsolationExceptionsFormFlyout'; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/translations.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/translations.ts new file mode 100644 index 0000000000000..df179c7a2221c --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/translations.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const NAME_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.name.placeholder', + { + defaultMessage: 'New IP', + } +); + +export const NAME_LABEL = i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.name.label', + { + defaultMessage: 'Name your host isolation exceptions', + } +); + +export const NAME_ERROR = i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.name.error', + { + defaultMessage: "The name can't be empty", + } +); + +export const DESCRIPTION_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.description.placeholder', + { + defaultMessage: 'Describe your Host Isolation Exception', + } +); + +export const DESCRIPTION_LABEL = i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.description.label', + { + defaultMessage: 'Description', + } +); + +export const IP_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.ip.placeholder', + { + defaultMessage: 'Ex 0.0.0.0/24', + } +); + +export const IP_LABEL = i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.ip.label', + { + defaultMessage: 'Enter IP Address', + } +); + +export const IP_ERROR = i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.ip.error', + { + defaultMessage: 'The ip is invalid. Only IPv4 with optional CIDR is supported', + } +); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx index 53b8bc33c252f..ac472fdae4d7b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx @@ -5,16 +5,18 @@ * 2.0. */ -import React from 'react'; import { act } from '@testing-library/react'; -import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { getFoundExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock'; import { HOST_ISOLATION_EXCEPTIONS_PATH } from '../../../../../common/constants'; -import { HostIsolationExceptionsList } from './host_isolation_exceptions_list'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; import { isFailedResourceState, isLoadedResourceState } from '../../../state'; import { getHostIsolationExceptionItems } from '../service'; -import { getFoundExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock'; +import { HostIsolationExceptionsList } from './host_isolation_exceptions_list'; jest.mock('../service'); + const getHostIsolationExceptionItemsMock = getHostIsolationExceptionItems as jest.Mock; describe('When on the host isolation exceptions page', () => { @@ -103,5 +105,17 @@ describe('When on the host isolation exceptions page', () => { ).toEqual(' Server is too far away'); }); }); + it('should show the create flyout when the add button is pressed', () => { + render(); + act(() => { + userEvent.click(renderResult.getByTestId('hostIsolationExceptionsListAddButton')); + }); + expect(renderResult.getByTestId('hostIsolationExceptionsCreateEditFlyout')).toBeTruthy(); + }); + it('should show the create flyout when the show location is create', () => { + history.push(`${HOST_ISOLATION_EXCEPTIONS_PATH}?show=create`); + render(); + expect(renderResult.getByTestId('hostIsolationExceptionsCreateEditFlyout')).toBeTruthy(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx index 53fb74d5bd8f7..cfb0121396e24 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx @@ -8,7 +8,7 @@ import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { i18n } from '@kbn/i18n'; import React, { Dispatch, useCallback } from 'react'; -import { EuiSpacer } from '@elastic/eui'; +import { EuiButton, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { useDispatch } from 'react-redux'; import { ExceptionItem } from '../../../../common/components/exceptions/viewer/exception_item'; @@ -32,6 +32,7 @@ import { ArtifactEntryCard, ArtifactEntryCardProps } from '../../../components/a import { HostIsolationExceptionsEmptyState } from './components/empty'; import { HostIsolationExceptionsPageAction } from '../store/action'; import { HostIsolationExceptionDeleteModal } from './components/delete_modal'; +import { HostIsolationExceptionsFormFlyout } from './components/form_flyout'; type HostIsolationExceptionPaginatedContent = PaginatedContentProps< Immutable, @@ -54,6 +55,8 @@ export const HostIsolationExceptionsList = () => { const dispatch = useDispatch>(); const itemToDelete = useHostIsolationExceptionsSelector(getItemToDelete); + const showFlyout = !!location.show; + const navigateCallback = useHostIsolationExceptionsNavigateCallback(); const handleOnSearch = useCallback( @@ -92,6 +95,15 @@ export const HostIsolationExceptionsList = () => { [navigateCallback] ); + const handleAddButtonClick = useCallback( + () => + navigateCallback({ + show: 'create', + id: undefined, + }), + [navigateCallback] + ); + return ( { defaultMessage="Host Isolation Exceptions" /> } - actions={[]} + actions={ + + + + } > + {showFlyout && } + { pagination={pagination} contentClassName="host-isolation-exceptions-container" data-test-subj="hostIsolationExceptionsContent" - noItemsMessage={} + noItemsMessage={} /> ); diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_risky_host_links/use_hosts_risk_score.ts b/x-pack/plugins/security_solution/public/overview/containers/overview_risky_host_links/use_hosts_risk_score.ts index 75cf51194ab65..af663bb74f54a 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_risky_host_links/use_hosts_risk_score.ts +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_risky_host_links/use_hosts_risk_score.ts @@ -12,12 +12,11 @@ import { useDispatch } from 'react-redux'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import { useKibana } from '../../../common/lib/kibana'; import { inputsActions } from '../../../common/store/actions'; - -import { HOST_RISK_SCORES_INDEX } from '../../../../common/constants'; import { isIndexNotFoundError } from '../../../common/utils/exceptions'; import { HostsRiskScore } from '../../../../common'; import { useHostsRiskScoreComplete } from './use_hosts_risk_score_complete'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; +import { getHostRiskIndex } from '../../../helpers'; export const QUERY_ID = 'host_risk_score'; const noop = () => {}; @@ -50,7 +49,7 @@ export const useHostsRiskScore = ({ const [loading, setLoading] = useState(riskyHostsFeatureEnabled); const { addError } = useAppToasts(); - const { data } = useKibana().services; + const { data, spaces } = useKibana().services; const dispatch = useDispatch(); @@ -99,14 +98,18 @@ export const useHostsRiskScore = ({ useEffect(() => { if (riskyHostsFeatureEnabled && (hostName || timerange)) { - start({ - data, - timerange: timerange ? { to: timerange.to, from: timerange.from, interval: '' } : undefined, - hostName, - defaultIndex: [HOST_RISK_SCORES_INDEX], + spaces.getActiveSpace().then((space) => { + start({ + data, + timerange: timerange + ? { to: timerange.to, from: timerange.from, interval: '' } + : undefined, + hostName, + defaultIndex: [getHostRiskIndex(space.id)], + }); }); } - }, [start, data, timerange, hostName, riskyHostsFeatureEnabled]); + }, [start, data, timerange, hostName, riskyHostsFeatureEnabled, spaces]); if ((!hostName && !timerange) || !riskyHostsFeatureEnabled) { return null; diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 3294d95bf909b..1cec87fd35d1f 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -9,6 +9,7 @@ import { CoreStart } from '../../../../src/core/public'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; +import { SpacesPluginStart } from '../../../plugins/spaces/public'; import { LensPublicStart } from '../../../plugins/lens/public'; import { NewsfeedPublicPluginStart } from '../../../../src/plugins/newsfeed/public'; import { Start as InspectorStart } from '../../../../src/plugins/inspector/public'; @@ -67,6 +68,7 @@ export interface StartPlugins { timelines: TimelinesUIStart; uiActions: UiActionsStart; ml?: MlPluginStart; + spaces: SpacesPluginStart; } export type StartServices = CoreStart & diff --git a/x-pack/plugins/security_solution/server/config.ts b/x-pack/plugins/security_solution/server/config.ts index 0850e43b21eda..bc5b43c6d25fd 100644 --- a/x-pack/plugins/security_solution/server/config.ts +++ b/x-pack/plugins/security_solution/server/config.ts @@ -12,6 +12,7 @@ import { getExperimentalAllowedValues, isValidExperimentalValue, } from '../common/experimental_features'; +import { UnderlyingLogClient } from './lib/detection_engine/rule_execution_log/types'; const allowedExperimentalValues = getExperimentalAllowedValues(); @@ -103,6 +104,19 @@ export const configSchema = schema.object({ }, }), + /** + * Rule Execution Log Configuration + */ + ruleExecutionLog: schema.object({ + underlyingClient: schema.oneOf( + [ + schema.literal(UnderlyingLogClient.eventLog), + schema.literal(UnderlyingLogClient.savedObjects), + ], + { defaultValue: UnderlyingLogClient.savedObjects } + ), + }), + /** * Host Endpoint Configuration */ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts index 1ac85f9a27969..2f401d27813ac 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/index.ts @@ -11,6 +11,7 @@ import { serverMock } from './server'; import { requestMock } from './request'; import { responseMock } from './response_factory'; import { ConfigType } from '../../../../config'; +import { UnderlyingLogClient } from '../../rule_execution_log/types'; export { requestMock, requestContextMock, responseMock, serverMock }; @@ -29,6 +30,9 @@ export const createMockConfig = (): ConfigType => ({ alertIgnoreFields: [], prebuiltRulesFromFileSystem: true, prebuiltRulesFromSavedObjects: false, + ruleExecutionLog: { + underlyingClient: UnderlyingLogClient.savedObjects, + }, }); export const mockGetCurrentUser = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/__mocks__/rule_execution_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/__mocks__/rule_execution_log_client.ts index bc9723e47a9d0..910e1ecaa508f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/__mocks__/rule_execution_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/__mocks__/rule_execution_log_client.ts @@ -14,7 +14,7 @@ export const ruleExecutionLogClientMock = { update: jest.fn(), delete: jest.fn(), logStatusChange: jest.fn(), - logExecutionMetric: jest.fn(), + logExecutionMetrics: jest.fn(), }), }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/constants.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/constants.ts new file mode 100644 index 0000000000000..f09eb43bf15f1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/constants.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const RULE_EXECUTION_LOG_PROVIDER = 'rule-execution.security'; + +export const ALERT_SAVED_OBJECT_TYPE = 'alert'; + +export enum RuleExecutionLogAction { + 'status-change' = 'status-change', + 'execution-metrics' = 'execution-metrics', +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts new file mode 100644 index 0000000000000..6b1a0cd5b18d0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IEventLogService } from '../../../../../../event_log/server'; +import { + FindBulkExecutionLogArgs, + FindExecutionLogArgs, + IRuleExecutionLogClient, + LogExecutionMetricsArgs, + LogStatusChangeArgs, + UpdateExecutionLogArgs, +} from '../types'; +import { EventLogClient } from './event_log_client'; + +export class EventLogAdapter implements IRuleExecutionLogClient { + private eventLogClient: EventLogClient; + + constructor(eventLogService: IEventLogService) { + this.eventLogClient = new EventLogClient(eventLogService); + } + + public async find({ ruleId, logsCount = 1, spaceId }: FindExecutionLogArgs) { + return []; // TODO Implement + } + + public async findBulk({ ruleIds, logsCount = 1, spaceId }: FindBulkExecutionLogArgs) { + return {}; // TODO Implement + } + + public async update({ attributes, spaceId, ruleName, ruleType }: UpdateExecutionLogArgs) { + // execution events are immutable, so we just log a status change istead of updating previous + if (attributes.status) { + this.eventLogClient.logStatusChange({ + ruleName, + ruleType, + ruleId: attributes.alertId, + newStatus: attributes.status, + spaceId, + }); + } + } + + public async delete(id: string) { + // execution events are immutable, nothing to do here + } + + public async logExecutionMetrics({ + ruleId, + spaceId, + ruleType, + ruleName, + metrics, + }: LogExecutionMetricsArgs) { + this.eventLogClient.logExecutionMetrics({ + ruleId, + ruleName, + ruleType, + spaceId, + metrics: { + executionGapDuration: metrics.executionGap?.asSeconds(), + totalIndexingDuration: metrics.indexingDurations?.reduce( + (acc, cur) => acc + Number(cur), + 0 + ), + totalSearchDuration: metrics.searchDurations?.reduce((acc, cur) => acc + Number(cur), 0), + }, + }); + } + + public async logStatusChange(args: LogStatusChangeArgs) { + if (args.metrics) { + this.logExecutionMetrics({ + ruleId: args.ruleId, + ruleName: args.ruleName, + ruleType: args.ruleType, + spaceId: args.spaceId, + metrics: args.metrics, + }); + } + + this.eventLogClient.logStatusChange(args); + } +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts new file mode 100644 index 0000000000000..d85c67e422035 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsUtils } from '../../../../../../../../src/core/server'; +import { + IEventLogger, + IEventLogService, + SAVED_OBJECT_REL_PRIMARY, +} from '../../../../../../event_log/server'; +import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas'; +import { LogStatusChangeArgs } from '../types'; +import { + RuleExecutionLogAction, + RULE_EXECUTION_LOG_PROVIDER, + ALERT_SAVED_OBJECT_TYPE, +} from './constants'; + +const spaceIdToNamespace = SavedObjectsUtils.namespaceStringToId; + +const statusSeverityDict: Record = { + [RuleExecutionStatus.succeeded]: 0, + [RuleExecutionStatus['going to run']]: 10, + [RuleExecutionStatus.warning]: 20, + [RuleExecutionStatus['partial failure']]: 20, + [RuleExecutionStatus.failed]: 30, +}; + +interface FindExecutionLogArgs { + ruleIds: string[]; + spaceId: string; + logsCount?: number; + statuses?: RuleExecutionStatus[]; +} + +interface LogExecutionMetricsArgs { + ruleId: string; + ruleName: string; + ruleType: string; + spaceId: string; + metrics: EventLogExecutionMetrics; +} + +interface EventLogExecutionMetrics { + totalSearchDuration?: number; + totalIndexingDuration?: number; + executionGapDuration?: number; +} + +interface IExecLogEventLogClient { + find: (args: FindExecutionLogArgs) => Promise<{}>; + logStatusChange: (args: LogStatusChangeArgs) => void; + logExecutionMetrics: (args: LogExecutionMetricsArgs) => void; +} + +export class EventLogClient implements IExecLogEventLogClient { + private sequence = 0; + private eventLogger: IEventLogger; + + constructor(eventLogService: IEventLogService) { + this.eventLogger = eventLogService.getLogger({ + event: { provider: RULE_EXECUTION_LOG_PROVIDER }, + }); + } + + public async find({ ruleIds, spaceId, statuses, logsCount = 1 }: FindExecutionLogArgs) { + return {}; // TODO implement + } + + public logExecutionMetrics({ + ruleId, + ruleName, + ruleType, + metrics, + spaceId, + }: LogExecutionMetricsArgs) { + this.eventLogger.logEvent({ + rule: { + id: ruleId, + name: ruleName, + category: ruleType, + }, + event: { + kind: 'metric', + action: RuleExecutionLogAction['execution-metrics'], + sequence: this.sequence++, + }, + kibana: { + alert: { + rule: { + execution: { + metrics: { + execution_gap_duration_s: metrics.executionGapDuration, + total_search_duration_ms: metrics.totalSearchDuration, + total_indexing_duration_ms: metrics.totalIndexingDuration, + }, + }, + }, + }, + space_ids: [spaceId], + saved_objects: [ + { + rel: SAVED_OBJECT_REL_PRIMARY, + type: ALERT_SAVED_OBJECT_TYPE, + id: ruleId, + namespace: spaceIdToNamespace(spaceId), + }, + ], + }, + }); + } + + public logStatusChange({ + ruleId, + ruleName, + ruleType, + newStatus, + message, + spaceId, + }: LogStatusChangeArgs) { + this.eventLogger.logEvent({ + rule: { + id: ruleId, + name: ruleName, + category: ruleType, + }, + event: { + kind: 'event', + action: RuleExecutionLogAction['status-change'], + sequence: this.sequence++, + }, + message, + kibana: { + alert: { + rule: { + execution: { + status: newStatus, + status_order: statusSeverityDict[newStatus], + }, + }, + }, + space_ids: [spaceId], + saved_objects: [ + { + rel: SAVED_OBJECT_REL_PRIMARY, + type: ALERT_SAVED_OBJECT_TYPE, + id: ruleId, + namespace: spaceIdToNamespace(spaceId), + }, + ], + }, + }); + } +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/register_event_log_provider.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/register_event_log_provider.ts new file mode 100644 index 0000000000000..7f28715198da6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/register_event_log_provider.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IEventLogService } from '../../../../../../event_log/server'; +import { RuleExecutionLogAction, RULE_EXECUTION_LOG_PROVIDER } from './constants'; + +export const registerEventLogProvider = (eventLogService: IEventLogService) => { + eventLogService.registerProviderActions( + RULE_EXECUTION_LOG_PROVIDER, + Object.keys(RuleExecutionLogAction) + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts index 135cefe2243b2..87a3b00cf4ed3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts @@ -6,34 +6,40 @@ */ import { SavedObjectsClientContract } from '../../../../../../../src/core/server'; -import { RuleRegistryAdapter } from './rule_registry_adapter/rule_registry_adapter'; +import { IEventLogService } from '../../../../../event_log/server'; +import { EventLogAdapter } from './event_log_adapter/event_log_adapter'; import { SavedObjectsAdapter } from './saved_objects_adapter/saved_objects_adapter'; import { - ExecutionMetric, - ExecutionMetricArgs, + LogExecutionMetricsArgs, FindBulkExecutionLogArgs, FindExecutionLogArgs, - IRuleDataPluginService, IRuleExecutionLogClient, LogStatusChangeArgs, UpdateExecutionLogArgs, + UnderlyingLogClient, } from './types'; export interface RuleExecutionLogClientArgs { - ruleDataService: IRuleDataPluginService; savedObjectsClient: SavedObjectsClientContract; + eventLogService: IEventLogService; + underlyingClient: UnderlyingLogClient; } -const RULE_REGISTRY_LOG_ENABLED = false; - export class RuleExecutionLogClient implements IRuleExecutionLogClient { private client: IRuleExecutionLogClient; - constructor({ ruleDataService, savedObjectsClient }: RuleExecutionLogClientArgs) { - if (RULE_REGISTRY_LOG_ENABLED) { - this.client = new RuleRegistryAdapter(ruleDataService); - } else { - this.client = new SavedObjectsAdapter(savedObjectsClient); + constructor({ + savedObjectsClient, + eventLogService, + underlyingClient, + }: RuleExecutionLogClientArgs) { + switch (underlyingClient) { + case UnderlyingLogClient.savedObjects: + this.client = new SavedObjectsAdapter(savedObjectsClient); + break; + case UnderlyingLogClient.eventLog: + this.client = new EventLogAdapter(eventLogService); + break; } } @@ -53,8 +59,8 @@ export class RuleExecutionLogClient implements IRuleExecutionLogClient { return this.client.delete(id); } - public async logExecutionMetric(args: ExecutionMetricArgs) { - return this.client.logExecutionMetric(args); + public async logExecutionMetrics(args: LogExecutionMetricsArgs) { + return this.client.logExecutionMetrics(args); } public async logStatusChange(args: LogStatusChangeArgs) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_adapter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_adapter.ts deleted file mode 100644 index ab8664ae995bf..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_adapter.ts +++ /dev/null @@ -1,106 +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 { merge } from 'lodash'; -import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas'; -import { RuleRegistryLogClient } from './rule_registry_log_client/rule_registry_log_client'; -import { - CreateExecutionLogArgs, - ExecutionMetric, - ExecutionMetricArgs, - FindBulkExecutionLogArgs, - FindExecutionLogArgs, - IRuleDataPluginService, - IRuleExecutionLogClient, - LogStatusChangeArgs, - UpdateExecutionLogArgs, -} from '../types'; - -/** - * @deprecated RuleRegistryAdapter is kept here only as a reference. It will be superseded with EventLog implementation - */ -export class RuleRegistryAdapter implements IRuleExecutionLogClient { - private ruleRegistryClient: RuleRegistryLogClient; - - constructor(ruleDataService: IRuleDataPluginService) { - this.ruleRegistryClient = new RuleRegistryLogClient(ruleDataService); - } - - public async find({ ruleId, logsCount = 1, spaceId }: FindExecutionLogArgs) { - const logs = await this.ruleRegistryClient.find({ - ruleIds: [ruleId], - logsCount, - spaceId, - }); - - return logs[ruleId].map((log) => ({ - id: '', - type: '', - score: 0, - attributes: log, - references: [], - })); - } - - public async findBulk({ ruleIds, logsCount = 1, spaceId }: FindBulkExecutionLogArgs) { - const [statusesById, lastErrorsById] = await Promise.all([ - this.ruleRegistryClient.find({ ruleIds, spaceId }), - this.ruleRegistryClient.find({ - ruleIds, - statuses: [RuleExecutionStatus.failed], - logsCount, - spaceId, - }), - ]); - return merge(statusesById, lastErrorsById); - } - - private async create({ attributes, spaceId }: CreateExecutionLogArgs) { - if (attributes.status) { - await this.ruleRegistryClient.logStatusChange({ - ruleId: attributes.alertId, - newStatus: attributes.status, - spaceId, - }); - } - - if (attributes.bulkCreateTimeDurations) { - await this.ruleRegistryClient.logExecutionMetric({ - ruleId: attributes.alertId, - metric: ExecutionMetric.indexingDurationMax, - value: Math.max(...attributes.bulkCreateTimeDurations.map(Number)), - spaceId, - }); - } - - if (attributes.gap) { - await this.ruleRegistryClient.logExecutionMetric({ - ruleId: attributes.alertId, - metric: ExecutionMetric.executionGap, - value: Number(attributes.gap), - spaceId, - }); - } - } - - public async update({ attributes, spaceId }: UpdateExecutionLogArgs) { - // execution events are immutable, so we just use 'create' here instead of 'update' - await this.create({ attributes, spaceId }); - } - - public async delete(id: string) { - // execution events are immutable, nothing to do here - } - - public async logExecutionMetric(args: ExecutionMetricArgs) { - return this.ruleRegistryClient.logExecutionMetric(args); - } - - public async logStatusChange(args: LogStatusChangeArgs) { - return this.ruleRegistryClient.logStatusChange(args); - } -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/constants.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/constants.ts deleted file mode 100644 index 8d74c71bf447d..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/constants.ts +++ /dev/null @@ -1,41 +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. - */ - -/** - * @deprecated EVENTS_INDEX_PREFIX is kept here only as a reference. It will be superseded with EventLog implementation - */ -export const EVENTS_INDEX_PREFIX = '.kibana_alerts-security.events'; - -/** - * @deprecated MESSAGE is kept here only as a reference. It will be superseded with EventLog implementation - */ -export const MESSAGE = 'message' as const; - -/** - * @deprecated EVENT_SEQUENCE is kept here only as a reference. It will be superseded with EventLog implementation - */ -export const EVENT_SEQUENCE = 'event.sequence' as const; - -/** - * @deprecated EVENT_DURATION is kept here only as a reference. It will be superseded with EventLog implementation - */ -export const EVENT_DURATION = 'event.duration' as const; - -/** - * @deprecated EVENT_END is kept here only as a reference. It will be superseded with EventLog implementation - */ -export const EVENT_END = 'event.end' as const; - -/** - * @deprecated RULE_STATUS is kept here only as a reference. It will be superseded with EventLog implementation - */ -export const RULE_STATUS = 'kibana.rac.detection_engine.rule_status' as const; - -/** - * @deprecated RULE_STATUS_SEVERITY is kept here only as a reference. It will be superseded with EventLog implementation - */ -export const RULE_STATUS_SEVERITY = 'kibana.rac.detection_engine.rule_status_severity' as const; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/parse_rule_execution_log.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/parse_rule_execution_log.ts deleted file mode 100644 index cbc6e570e936f..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/parse_rule_execution_log.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isLeft } from 'fp-ts/lib/Either'; -import { PathReporter } from 'io-ts/lib/PathReporter'; -import { technicalRuleFieldMap } from '../../../../../../../rule_registry/common/assets/field_maps/technical_rule_field_map'; -import { - mergeFieldMaps, - runtimeTypeFromFieldMap, -} from '../../../../../../../rule_registry/common/field_map'; -import { ruleExecutionFieldMap } from './rule_execution_field_map'; - -const ruleExecutionLogRuntimeType = runtimeTypeFromFieldMap( - mergeFieldMaps(technicalRuleFieldMap, ruleExecutionFieldMap) -); - -/** - * @deprecated parseRuleExecutionLog is kept here only as a reference. It will be superseded with EventLog implementation - */ -export const parseRuleExecutionLog = (input: unknown) => { - const validate = ruleExecutionLogRuntimeType.decode(input); - - if (isLeft(validate)) { - throw new Error(PathReporter.report(validate).join('\n')); - } - - return ruleExecutionLogRuntimeType.encode(validate.right); -}; - -/** - * @deprecated RuleExecutionEvent is kept here only as a reference. It will be superseded with EventLog implementation - * - * It's marked as `Partial` because the field map is not yet appropriate for - * execution log events. - */ -export type RuleExecutionEvent = Partial>; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_execution_field_map.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_execution_field_map.ts deleted file mode 100644 index b3c70cd56d9e6..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_execution_field_map.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - EVENT_DURATION, - EVENT_END, - EVENT_SEQUENCE, - MESSAGE, - RULE_STATUS, - RULE_STATUS_SEVERITY, -} from './constants'; - -/** - * @deprecated ruleExecutionFieldMap is kept here only as a reference. It will be superseded with EventLog implementation - */ -export const ruleExecutionFieldMap = { - [MESSAGE]: { type: 'keyword' }, - [EVENT_SEQUENCE]: { type: 'long' }, - [EVENT_END]: { type: 'date' }, - [EVENT_DURATION]: { type: 'long' }, - [RULE_STATUS]: { type: 'keyword' }, - [RULE_STATUS_SEVERITY]: { type: 'integer' }, -} as const; - -/** - * @deprecated RuleExecutionFieldMap is kept here only as a reference. It will be superseded with EventLog implementation - */ -export type RuleExecutionFieldMap = typeof ruleExecutionFieldMap; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts deleted file mode 100644 index 3cd6171b5bbeb..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts +++ /dev/null @@ -1,270 +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 { estypes } from '@elastic/elasticsearch'; -import { - ALERT_RULE_CONSUMER, - ALERT_RULE_TYPE_ID, - EVENT_ACTION, - EVENT_KIND, - SPACE_IDS, - TIMESTAMP, - ALERT_RULE_UUID, -} from '@kbn/rule-data-utils'; -import moment from 'moment'; - -import { mappingFromFieldMap } from '../../../../../../../rule_registry/common/mapping_from_field_map'; -import { Dataset, IRuleDataClient } from '../../../../../../../rule_registry/server'; -import { SERVER_APP_ID } from '../../../../../../common/constants'; -import { RuleExecutionStatus } from '../../../../../../common/detection_engine/schemas/common/schemas'; -import { invariant } from '../../../../../../common/utils/invariant'; -import { IRuleStatusSOAttributes } from '../../../rules/types'; -import { makeFloatString } from '../../../signals/utils'; -import { - ExecutionMetric, - ExecutionMetricArgs, - IRuleDataPluginService, - LogStatusChangeArgs, -} from '../../types'; -import { EVENT_SEQUENCE, MESSAGE, RULE_STATUS, RULE_STATUS_SEVERITY } from './constants'; -import { parseRuleExecutionLog, RuleExecutionEvent } from './parse_rule_execution_log'; -import { ruleExecutionFieldMap } from './rule_execution_field_map'; -import { - getLastEntryAggregation, - getMetricAggregation, - getMetricField, - sortByTimeDesc, -} from './utils'; - -const statusSeverityDict: Record = { - [RuleExecutionStatus.succeeded]: 0, - [RuleExecutionStatus['going to run']]: 10, - [RuleExecutionStatus.warning]: 20, - [RuleExecutionStatus['partial failure']]: 20, - [RuleExecutionStatus.failed]: 30, -}; - -interface FindExecutionLogArgs { - ruleIds: string[]; - spaceId: string; - logsCount?: number; - statuses?: RuleExecutionStatus[]; -} - -interface IRuleRegistryLogClient { - find: (args: FindExecutionLogArgs) => Promise<{ - [ruleId: string]: IRuleStatusSOAttributes[] | undefined; - }>; - create: (event: RuleExecutionEvent) => Promise; - logStatusChange: (args: LogStatusChangeArgs) => Promise; - logExecutionMetric: (args: ExecutionMetricArgs) => Promise; -} - -/** - * @deprecated RuleRegistryLogClient is kept here only as a reference. It will be superseded with EventLog implementation - */ -export class RuleRegistryLogClient implements IRuleRegistryLogClient { - private sequence = 0; - private ruleDataClient: IRuleDataClient; - - constructor(ruleDataService: IRuleDataPluginService) { - this.ruleDataClient = ruleDataService.initializeIndex({ - feature: SERVER_APP_ID, - registrationContext: 'security', - dataset: Dataset.events, - componentTemplateRefs: [], - componentTemplates: [ - { - name: 'mappings', - mappings: mappingFromFieldMap(ruleExecutionFieldMap, 'strict'), - }, - ], - }); - } - - public async find({ ruleIds, spaceId, statuses, logsCount = 1 }: FindExecutionLogArgs) { - if (ruleIds.length === 0) { - return {}; - } - - const filter: estypes.QueryDslQueryContainer[] = [ - { terms: { [ALERT_RULE_UUID]: ruleIds } }, - { terms: { [SPACE_IDS]: [spaceId] } }, - ]; - - if (statuses) { - filter.push({ terms: { [RULE_STATUS]: statuses } }); - } - - const result = await this.ruleDataClient.getReader().search({ - size: 0, - body: { - query: { - bool: { - filter, - }, - }, - aggs: { - rules: { - terms: { - field: ALERT_RULE_UUID, - size: ruleIds.length, - }, - aggs: { - most_recent_logs: { - top_hits: { - sort: sortByTimeDesc, - size: logsCount, - }, - }, - last_failure: getLastEntryAggregation(RuleExecutionStatus.failed), - last_success: getLastEntryAggregation(RuleExecutionStatus.succeeded), - execution_gap: getMetricAggregation(ExecutionMetric.executionGap), - search_duration_max: getMetricAggregation(ExecutionMetric.searchDurationMax), - indexing_duration_max: getMetricAggregation(ExecutionMetric.indexingDurationMax), - indexing_lookback: getMetricAggregation(ExecutionMetric.indexingLookback), - }, - }, - }, - }, - }); - - if (result.hits.total.value === 0) { - return {}; - } - - invariant(result.aggregations, 'Search response should contain aggregations'); - - return Object.fromEntries( - result.aggregations.rules.buckets.map<[ruleId: string, logs: IRuleStatusSOAttributes[]]>( - (bucket) => [ - bucket.key as string, - bucket.most_recent_logs.hits.hits.map((event) => { - const logEntry = parseRuleExecutionLog(event._source); - invariant( - logEntry[ALERT_RULE_UUID] ?? '', - 'Malformed execution log entry: rule.id field not found' - ); - - const lastFailure = bucket.last_failure.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.last_failure.event.hits.hits[0]._source) - : undefined; - - const lastSuccess = bucket.last_success.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.last_success.event.hits.hits[0]._source) - : undefined; - - const lookBack = bucket.indexing_lookback.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.indexing_lookback.event.hits.hits[0]._source) - : undefined; - - const executionGap = bucket.execution_gap.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.execution_gap.event.hits.hits[0]._source)[ - getMetricField(ExecutionMetric.executionGap) - ] - : undefined; - - const searchDuration = bucket.search_duration_max.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.search_duration_max.event.hits.hits[0]._source)[ - getMetricField(ExecutionMetric.searchDurationMax) - ] - : undefined; - - const indexingDuration = bucket.indexing_duration_max.event.hits.hits[0] - ? parseRuleExecutionLog(bucket.indexing_duration_max.event.hits.hits[0]._source)[ - getMetricField(ExecutionMetric.indexingDurationMax) - ] - : undefined; - - const alertId = logEntry[ALERT_RULE_UUID] ?? ''; - const statusDate = logEntry[TIMESTAMP]; - const lastFailureAt = lastFailure?.[TIMESTAMP]; - const lastFailureMessage = lastFailure?.[MESSAGE]; - const lastSuccessAt = lastSuccess?.[TIMESTAMP]; - const lastSuccessMessage = lastSuccess?.[MESSAGE]; - const status = (logEntry[RULE_STATUS] as RuleExecutionStatus) || null; - const lastLookBackDate = lookBack?.[getMetricField(ExecutionMetric.indexingLookback)]; - const gap = executionGap ? moment.duration(executionGap).humanize() : null; - const bulkCreateTimeDurations = indexingDuration - ? [makeFloatString(indexingDuration)] - : null; - const searchAfterTimeDurations = searchDuration - ? [makeFloatString(searchDuration)] - : null; - - return { - alertId, - statusDate, - lastFailureAt, - lastFailureMessage, - lastSuccessAt, - lastSuccessMessage, - status, - lastLookBackDate, - gap, - bulkCreateTimeDurations, - searchAfterTimeDurations, - }; - }), - ] - ) - ); - } - - public async logExecutionMetric({ - ruleId, - namespace, - metric, - value, - spaceId, - }: ExecutionMetricArgs) { - await this.create( - { - [SPACE_IDS]: [spaceId], - [EVENT_ACTION]: metric, - [EVENT_KIND]: 'metric', - [getMetricField(metric)]: value, - [ALERT_RULE_UUID]: ruleId ?? '', - [TIMESTAMP]: new Date().toISOString(), - [ALERT_RULE_CONSUMER]: SERVER_APP_ID, - [ALERT_RULE_TYPE_ID]: SERVER_APP_ID, - }, - namespace - ); - } - - public async logStatusChange({ - ruleId, - newStatus, - namespace, - message, - spaceId, - }: LogStatusChangeArgs) { - await this.create( - { - [SPACE_IDS]: [spaceId], - [EVENT_ACTION]: 'status-change', - [EVENT_KIND]: 'event', - [EVENT_SEQUENCE]: this.sequence++, - [MESSAGE]: message, - [ALERT_RULE_UUID]: ruleId ?? '', - [RULE_STATUS_SEVERITY]: statusSeverityDict[newStatus], - [RULE_STATUS]: newStatus, - [TIMESTAMP]: new Date().toISOString(), - [ALERT_RULE_CONSUMER]: SERVER_APP_ID, - [ALERT_RULE_TYPE_ID]: SERVER_APP_ID, - }, - namespace - ); - } - - public async create(event: RuleExecutionEvent, namespace?: string) { - await this.ruleDataClient.getWriter({ namespace }).bulk({ - body: [{ index: {} }, event], - }); - } -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/utils.ts deleted file mode 100644 index 713cf73890e7f..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/utils.ts +++ /dev/null @@ -1,76 +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 { SearchSort } from '@elastic/elasticsearch/api/types'; -import { EVENT_ACTION, TIMESTAMP } from '@kbn/rule-data-utils'; -import { RuleExecutionStatus } from '../../../../../../common/detection_engine/schemas/common/schemas'; -import { ExecutionMetric } from '../../types'; -import { RULE_STATUS, EVENT_SEQUENCE, EVENT_DURATION, EVENT_END } from './constants'; - -const METRIC_FIELDS = { - [ExecutionMetric.executionGap]: EVENT_DURATION, - [ExecutionMetric.searchDurationMax]: EVENT_DURATION, - [ExecutionMetric.indexingDurationMax]: EVENT_DURATION, - [ExecutionMetric.indexingLookback]: EVENT_END, -}; - -/** - * Returns ECS field in which metric value is stored - * @deprecated getMetricField is kept here only as a reference. It will be superseded with EventLog implementation - * - * @param metric - execution metric - * @returns ECS field - */ -export const getMetricField = (metric: T) => METRIC_FIELDS[metric]; - -/** - * @deprecated sortByTimeDesc is kept here only as a reference. It will be superseded with EventLog implementation - */ -export const sortByTimeDesc: SearchSort = [{ [TIMESTAMP]: 'desc' }, { [EVENT_SEQUENCE]: 'desc' }]; - -/** - * Builds aggregation to retrieve the most recent metric value - * @deprecated getMetricAggregation is kept here only as a reference. It will be superseded with EventLog implementation - * - * @param metric - execution metric - * @returns aggregation - */ -export const getMetricAggregation = (metric: ExecutionMetric) => ({ - filter: { - term: { [EVENT_ACTION]: metric }, - }, - aggs: { - event: { - top_hits: { - size: 1, - sort: sortByTimeDesc, - _source: [TIMESTAMP, getMetricField(metric)], - }, - }, - }, -}); - -/** - * Builds aggregation to retrieve the most recent log entry with the given status - * @deprecated getLastEntryAggregation is kept here only as a reference. It will be superseded with EventLog implementation - * - * @param status - rule execution status - * @returns aggregation - */ -export const getLastEntryAggregation = (status: RuleExecutionStatus) => ({ - filter: { - term: { [RULE_STATUS]: status }, - }, - aggs: { - event: { - top_hits: { - sort: sortByTimeDesc, - size: 1, - }, - }, - }, -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts index 27329ebf8f90c..ca806bd58e369 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts @@ -14,12 +14,11 @@ import { ruleStatusSavedObjectsClientFactory, } from './rule_status_saved_objects_client'; import { - ExecutionMetric, - ExecutionMetricArgs, + LogExecutionMetricsArgs, FindBulkExecutionLogArgs, FindExecutionLogArgs, IRuleExecutionLogClient, - LegacyMetrics, + ExecutionMetrics, LogStatusChangeArgs, UpdateExecutionLogArgs, } from '../types'; @@ -28,14 +27,16 @@ import { assertUnreachable } from '../../../../../common'; // 1st is mutable status, followed by 5 most recent failures export const MAX_RULE_STATUSES = 6; -const METRIC_FIELDS = { - [ExecutionMetric.executionGap]: 'gap', - [ExecutionMetric.searchDurationMax]: 'searchAfterTimeDurations', - [ExecutionMetric.indexingDurationMax]: 'bulkCreateTimeDurations', - [ExecutionMetric.indexingLookback]: 'lastLookBackDate', -} as const; - -const getMetricField = (metric: T) => METRIC_FIELDS[metric]; +const convertMetricFields = ( + metrics: ExecutionMetrics +): Pick< + IRuleStatusSOAttributes, + 'gap' | 'searchAfterTimeDurations' | 'bulkCreateTimeDurations' +> => ({ + gap: metrics.executionGap?.humanize(), + searchAfterTimeDurations: metrics.searchDurations, + bulkCreateTimeDurations: metrics.indexingDurations, +}); export class SavedObjectsAdapter implements IRuleExecutionLogClient { private ruleStatusClient: RuleStatusSavedObjectsClient; @@ -66,16 +67,12 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient { await this.ruleStatusClient.delete(id); } - public async logExecutionMetric({ - ruleId, - metric, - value, - }: ExecutionMetricArgs) { + public async logExecutionMetrics({ ruleId, metrics }: LogExecutionMetricsArgs) { const [currentStatus] = await this.getOrCreateRuleStatuses(ruleId); await this.ruleStatusClient.update(currentStatus.id, { ...currentStatus.attributes, - [getMetricField(metric)]: value, + ...convertMetricFields(metrics), }); } @@ -158,11 +155,11 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient { const buildRuleStatusAttributes: ( status: RuleExecutionStatus, message?: string, - metrics?: LegacyMetrics + metrics?: ExecutionMetrics ) => Partial = (status, message, metrics = {}) => { const now = new Date().toISOString(); const baseAttributes: Partial = { - ...metrics, + ...convertMetricFields(metrics), status: status === RuleExecutionStatus.warning ? RuleExecutionStatus['partial failure'] : status, statusDate: now, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/types.ts index 9c66032f681de..e38f974ddee2e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/types.ts @@ -5,28 +5,16 @@ * 2.0. */ -import { PublicMethodsOf } from '@kbn/utility-types'; +import { Duration } from 'moment'; import { SavedObjectsFindResult } from '../../../../../../../src/core/server'; -import { RuleDataPluginService } from '../../../../../rule_registry/server'; import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas'; import { IRuleStatusSOAttributes } from '../rules/types'; -export enum ExecutionMetric { - 'executionGap' = 'executionGap', - 'searchDurationMax' = 'searchDurationMax', - 'indexingDurationMax' = 'indexingDurationMax', - 'indexingLookback' = 'indexingLookback', +export enum UnderlyingLogClient { + 'savedObjects' = 'savedObjects', + 'eventLog' = 'eventLog', } -export type IRuleDataPluginService = PublicMethodsOf; - -export type ExecutionMetricValue = { - [ExecutionMetric.executionGap]: number; - [ExecutionMetric.searchDurationMax]: number; - [ExecutionMetric.indexingDurationMax]: number; - [ExecutionMetric.indexingLookback]: Date; -}[T]; - export interface FindExecutionLogArgs { ruleId: string; spaceId: string; @@ -39,29 +27,34 @@ export interface FindBulkExecutionLogArgs { logsCount?: number; } -/** - * @deprecated LegacyMetrics are only kept here for backward compatibility - * and should be replaced by ExecutionMetric in the future - */ -export interface LegacyMetrics { - searchAfterTimeDurations?: string[]; - bulkCreateTimeDurations?: string[]; +export interface ExecutionMetrics { + searchDurations?: string[]; + indexingDurations?: string[]; + /** + * @deprecated lastLookBackDate is logged only by SavedObjectsAdapter and should be removed in the future + */ lastLookBackDate?: string; - gap?: string; + executionGap?: Duration; } export interface LogStatusChangeArgs { ruleId: string; + ruleName: string; + ruleType: string; spaceId: string; newStatus: RuleExecutionStatus; - namespace?: string; message?: string; - metrics?: LegacyMetrics; + /** + * @deprecated Use RuleExecutionLogClient.logExecutionMetrics to write metrics instead + */ + metrics?: ExecutionMetrics; } export interface UpdateExecutionLogArgs { id: string; attributes: IRuleStatusSOAttributes; + ruleName: string; + ruleType: string; spaceId: string; } @@ -70,12 +63,12 @@ export interface CreateExecutionLogArgs { spaceId: string; } -export interface ExecutionMetricArgs { +export interface LogExecutionMetricsArgs { ruleId: string; + ruleName: string; + ruleType: string; spaceId: string; - namespace?: string; - metric: T; - value: ExecutionMetricValue; + metrics: ExecutionMetrics; } export interface FindBulkExecutionLogResponse { @@ -90,5 +83,5 @@ export interface IRuleExecutionLogClient { update: (args: UpdateExecutionLogArgs) => Promise; delete: (id: string) => Promise; logStatusChange: (args: LogStatusChangeArgs) => Promise; - logExecutionMetric: (args: ExecutionMetricArgs) => Promise; + logExecutionMetrics: (args: LogExecutionMetricsArgs) => Promise; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts index bcab8a0af5ffb..c6f818f04fc5d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts @@ -14,6 +14,7 @@ import { mlPluginServerMock } from '../../../../../../ml/server/mocks'; import type { IRuleDataClient } from '../../../../../../rule_registry/server'; import { ruleRegistryMocks } from '../../../../../../rule_registry/server/mocks'; +import { eventLogServiceMock } from '../../../../../../event_log/server/mocks'; import { PluginSetupContract as AlertingPluginSetupContract } from '../../../../../../alerting/server'; import { ConfigType } from '../../../../config'; import { AlertAttributes } from '../../signals/types'; @@ -55,6 +56,7 @@ export const createRuleTypeMocks = ( references: [], attributes: { actions: [], + alertTypeId: 'siem.signals', enabled: true, name: 'mock rule', tags: [], @@ -89,7 +91,7 @@ export const createRuleTypeMocks = ( ruleDataClient: ruleRegistryMocks.createRuleDataClient( '.alerts-security.alerts' ) as IRuleDataClient, - ruleDataService: ruleRegistryMocks.createRuleDataPluginService(), + eventLogService: eventLogServiceMock.create(), }, services, scheduleActions, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts index df15d4b2c0112..9ea36abe997c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts @@ -40,8 +40,9 @@ import { scheduleThrottledNotificationActions } from '../notifications/schedule_ /* eslint-disable complexity */ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = - ({ lists, logger, mergeStrategy, ignoreFields, ruleDataClient, ruleDataService }) => + ({ lists, logger, config, ruleDataClient, eventLogService }) => (type) => { + const { alertIgnoreFields: ignoreFields, alertMergeStrategy: mergeStrategy } = config; const persistenceRuleType = createPersistenceRuleTypeFactory({ ruleDataClient, logger }); return persistenceRuleType({ ...type, @@ -65,13 +66,15 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = const ruleStatusClient = new RuleExecutionLogClient({ savedObjectsClient, - ruleDataService, + eventLogService, + underlyingClient: config.ruleExecutionLog.underlyingClient, }); const ruleSO = await savedObjectsClient.get('alert', alertId); const { actions, name, + alertTypeId, schedule: { interval }, } = ruleSO.attributes; const refresh = actions.length ? 'wait_for' : false; @@ -87,9 +90,14 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = logger.debug(buildRuleMessage(`interval: ${interval}`)); let wroteWarningStatus = false; - await ruleStatusClient.logStatusChange({ + const basicLogArguments = { spaceId, ruleId: alertId, + ruleName: name, + ruleType: alertTypeId, + }; + await ruleStatusClient.logStatusChange({ + ...basicLogArguments, newStatus: RuleExecutionStatus['going to run'], }); @@ -125,8 +133,7 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = tryCatch( () => hasReadIndexPrivileges({ - spaceId, - ruleId: alertId, + ...basicLogArguments, privileges, logger, buildRuleMessage, @@ -138,8 +145,7 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = tryCatch( () => hasTimestampFields({ - spaceId, - ruleId: alertId, + ...basicLogArguments, wroteStatus: wroteStatus as boolean, timestampField: hasTimestampOverride ? (timestampOverride as string) @@ -179,11 +185,10 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = logger.warn(gapMessage); hasError = true; await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, + ...basicLogArguments, newStatus: RuleExecutionStatus.failed, message: gapMessage, - metrics: { gap: gapString }, + metrics: { executionGap: remainingGap }, }); } @@ -262,8 +267,7 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = if (result.warningMessages.length) { const warningMessage = buildRuleMessage(result.warningMessages.join()); await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, + ...basicLogArguments, newStatus: RuleExecutionStatus['partial failure'], message: warningMessage, }); @@ -327,13 +331,12 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = if (!hasError && !wroteWarningStatus && !result.warning) { await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, + ...basicLogArguments, newStatus: RuleExecutionStatus.succeeded, message: 'succeeded', metrics: { - bulkCreateTimeDurations: result.bulkCreateTimes, - searchAfterTimeDurations: result.searchAfterTimes, + indexingDurations: result.bulkCreateTimes, + searchDurations: result.searchAfterTimes, lastLookBackDate: result.lastLookbackDate?.toISOString(), }, }); @@ -356,13 +359,12 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = ); logger.error(errorMessage); await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, + ...basicLogArguments, newStatus: RuleExecutionStatus.failed, message: errorMessage, metrics: { - bulkCreateTimeDurations: result.bulkCreateTimes, - searchAfterTimeDurations: result.searchAfterTimes, + indexingDurations: result.bulkCreateTimes, + searchDurations: result.searchAfterTimes, lastLookBackDate: result.lastLookbackDate?.toISOString(), }, }); @@ -376,13 +378,12 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = logger.error(message); await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, + ...basicLogArguments, newStatus: RuleExecutionStatus.failed, message, metrics: { - bulkCreateTimeDurations: result.bulkCreateTimes, - searchAfterTimeDurations: result.searchAfterTimes, + indexingDurations: result.bulkCreateTimes, + searchDurations: result.searchAfterTimes, lastLookBackDate: result.lastLookbackDate?.toISOString(), }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.test.ts index 868419179c76b..43860d396ac5d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.test.ts @@ -12,6 +12,7 @@ import { allowedExperimentalValues } from '../../../../../common/experimental_fe import { createEqlAlertType } from './create_eql_alert_type'; import { createRuleTypeMocks } from '../__mocks__/rule_type'; import { getEqlRuleParams } from '../../schemas/rule_schemas.mock'; +import { createMockConfig } from '../../routes/__mocks__'; jest.mock('../../rule_execution_log/rule_execution_log_client'); @@ -26,10 +27,9 @@ describe('Event correlation alerts', () => { experimentalFeatures: allowedExperimentalValues, lists: dependencies.lists, logger: dependencies.logger, - ignoreFields: [], - mergeStrategy: 'allFields', + config: createMockConfig(), ruleDataClient: dependencies.ruleDataClient, - ruleDataService: dependencies.ruleDataService, + eventLogService: dependencies.eventLogService, version: '1.0.0', }); dependencies.alerting.registerType(eqlAlertType); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts index 5893c6fdc86c2..9324b469bf644 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts @@ -14,23 +14,14 @@ import { createSecurityRuleTypeFactory } from '../create_security_rule_type_fact import { CreateRuleOptions } from '../types'; export const createEqlAlertType = (createOptions: CreateRuleOptions) => { - const { - experimentalFeatures, - lists, - logger, - ignoreFields, - mergeStrategy, - ruleDataClient, - version, - ruleDataService, - } = createOptions; + const { experimentalFeatures, lists, logger, config, ruleDataClient, version, eventLogService } = + createOptions; const createSecurityRuleType = createSecurityRuleTypeFactory({ lists, logger, - ignoreFields, - mergeStrategy, + config, ruleDataClient, - ruleDataService, + eventLogService, }); return createSecurityRuleType({ id: EQL_RULE_TYPE_ID, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts index 6daafbfae40f2..a7accc4ae8a0f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts @@ -44,6 +44,7 @@ describe('buildAlert', () => { const ruleSO = { attributes: { actions: [], + alertTypeId: 'siem.signals', createdAt: new Date().toISOString(), createdBy: 'gandalf', params: getQueryRuleParams(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.test.ts index fe836c872dcad..3db4f5686abdc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.test.ts @@ -16,6 +16,7 @@ import { createIndicatorMatchAlertType } from './create_indicator_match_alert_ty import { sampleDocNoSortId } from '../../signals/__mocks__/es_results'; import { CountResponse } from 'kibana/server'; import { RuleParams } from '../../schemas/rule_schemas'; +import { createMockConfig } from '../../routes/__mocks__'; jest.mock('../utils/get_list_client', () => ({ getListClient: jest.fn().mockReturnValue({ @@ -56,10 +57,9 @@ describe('Indicator Match Alerts', () => { experimentalFeatures: allowedExperimentalValues, lists: dependencies.lists, logger: dependencies.logger, - ignoreFields: [], - mergeStrategy: 'allFields', + config: createMockConfig(), ruleDataClient: dependencies.ruleDataClient, - ruleDataService: dependencies.ruleDataService, + eventLogService: dependencies.eventLogService, version: '1.0.0', }); @@ -97,10 +97,9 @@ describe('Indicator Match Alerts', () => { experimentalFeatures: allowedExperimentalValues, lists: dependencies.lists, logger: dependencies.logger, - mergeStrategy: 'allFields', - ignoreFields: [], + config: createMockConfig(), ruleDataClient: dependencies.ruleDataClient, - ruleDataService: dependencies.ruleDataService, + eventLogService: dependencies.eventLogService, version: '1.0.0', }); @@ -136,10 +135,9 @@ describe('Indicator Match Alerts', () => { experimentalFeatures: allowedExperimentalValues, lists: dependencies.lists, logger: dependencies.logger, - mergeStrategy: 'allFields', - ignoreFields: [], + config: createMockConfig(), ruleDataClient: dependencies.ruleDataClient, - ruleDataService: dependencies.ruleDataService, + eventLogService: dependencies.eventLogService, version: '1.0.0', }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts index e2d5da1def707..c30fdd7d99c2a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts @@ -14,23 +14,14 @@ import { createSecurityRuleTypeFactory } from '../create_security_rule_type_fact import { CreateRuleOptions } from '../types'; export const createIndicatorMatchAlertType = (createOptions: CreateRuleOptions) => { - const { - experimentalFeatures, - lists, - logger, - mergeStrategy, - ignoreFields, - ruleDataClient, - version, - ruleDataService, - } = createOptions; + const { experimentalFeatures, lists, logger, config, ruleDataClient, version, eventLogService } = + createOptions; const createSecurityRuleType = createSecurityRuleTypeFactory({ lists, logger, - mergeStrategy, - ignoreFields, + config, ruleDataClient, - ruleDataService, + eventLogService, }); return createSecurityRuleType({ id: INDICATOR_RULE_TYPE_ID, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.test.ts index 23cd2e94aedf8..bffc20c3df1e3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.test.ts @@ -14,6 +14,7 @@ import { createRuleTypeMocks } from '../__mocks__/rule_type'; import { createMlAlertType } from './create_ml_alert_type'; import { RuleParams } from '../../schemas/rule_schemas'; +import { createMockConfig } from '../../routes/__mocks__'; jest.mock('../../signals/bulk_create_ml_signals'); @@ -97,11 +98,10 @@ describe('Machine Learning Alerts', () => { experimentalFeatures: allowedExperimentalValues, lists: dependencies.lists, logger: dependencies.logger, - mergeStrategy: 'allFields', - ignoreFields: [], + config: createMockConfig(), ml: mlMock, ruleDataClient: dependencies.ruleDataClient, - ruleDataService: dependencies.ruleDataService, + eventLogService: dependencies.eventLogService, version: '1.0.0', }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts index ec2f5dd104646..ac2d3f14831a4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts @@ -14,15 +14,13 @@ import { createSecurityRuleTypeFactory } from '../create_security_rule_type_fact import { CreateRuleOptions } from '../types'; export const createMlAlertType = (createOptions: CreateRuleOptions) => { - const { lists, logger, mergeStrategy, ignoreFields, ml, ruleDataClient, ruleDataService } = - createOptions; + const { lists, logger, config, ml, ruleDataClient, eventLogService } = createOptions; const createSecurityRuleType = createSecurityRuleTypeFactory({ lists, logger, - mergeStrategy, - ignoreFields, + config, ruleDataClient, - ruleDataService, + eventLogService, }); return createSecurityRuleType({ id: ML_RULE_TYPE_ID, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts index e45d8440386fe..4fdeac8047b1d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts @@ -14,6 +14,7 @@ import { allowedExperimentalValues } from '../../../../../common/experimental_fe import { sampleDocNoSortId } from '../../signals/__mocks__/es_results'; import { createQueryAlertType } from './create_query_alert_type'; import { createRuleTypeMocks } from '../__mocks__/rule_type'; +import { createMockConfig } from '../../routes/__mocks__'; jest.mock('../utils/get_list_client', () => ({ getListClient: jest.fn().mockReturnValue({ @@ -31,10 +32,9 @@ describe('Custom Query Alerts', () => { experimentalFeatures: allowedExperimentalValues, lists: dependencies.lists, logger: dependencies.logger, - mergeStrategy: 'allFields', - ignoreFields: [], + config: createMockConfig(), ruleDataClient: dependencies.ruleDataClient, - ruleDataService: dependencies.ruleDataService, + eventLogService: dependencies.eventLogService, version: '1.0.0', }); @@ -79,10 +79,9 @@ describe('Custom Query Alerts', () => { experimentalFeatures: allowedExperimentalValues, lists: dependencies.lists, logger: dependencies.logger, - mergeStrategy: 'allFields', - ignoreFields: [], + config: createMockConfig(), ruleDataClient: dependencies.ruleDataClient, - ruleDataService: dependencies.ruleDataService, + eventLogService: dependencies.eventLogService, version: '1.0.0', }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts index d5af7a4c8b5a4..469c237112dcb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts @@ -14,23 +14,14 @@ import { createSecurityRuleTypeFactory } from '../create_security_rule_type_fact import { CreateRuleOptions } from '../types'; export const createQueryAlertType = (createOptions: CreateRuleOptions) => { - const { - experimentalFeatures, - lists, - logger, - mergeStrategy, - ignoreFields, - ruleDataClient, - version, - ruleDataService, - } = createOptions; + const { experimentalFeatures, lists, logger, config, ruleDataClient, version, eventLogService } = + createOptions; const createSecurityRuleType = createSecurityRuleTypeFactory({ lists, logger, - mergeStrategy, - ignoreFields, + config, ruleDataClient, - ruleDataService, + eventLogService, }); return createSecurityRuleType({ id: QUERY_RULE_TYPE_ID, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.test.ts index 74435cb300472..aff57dbdf3cd4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.test.ts @@ -9,6 +9,7 @@ import { allowedExperimentalValues } from '../../../../../common/experimental_fe import { createThresholdAlertType } from './create_threshold_alert_type'; import { createRuleTypeMocks } from '../__mocks__/rule_type'; import { getThresholdRuleParams } from '../../schemas/rule_schemas.mock'; +import { createMockConfig } from '../../routes/__mocks__'; jest.mock('../../rule_execution_log/rule_execution_log_client'); @@ -20,10 +21,9 @@ describe('Threshold Alerts', () => { experimentalFeatures: allowedExperimentalValues, lists: dependencies.lists, logger: dependencies.logger, - mergeStrategy: 'allFields', - ignoreFields: [], + config: createMockConfig(), ruleDataClient: dependencies.ruleDataClient, - ruleDataService: dependencies.ruleDataService, + eventLogService: dependencies.eventLogService, version: '1.0.0', }); dependencies.alerting.registerType(thresholdAlertTpe); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts index a503cf5aedbea..789e4525c58ab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts @@ -16,23 +16,14 @@ import { createSecurityRuleTypeFactory } from '../create_security_rule_type_fact import { CreateRuleOptions } from '../types'; export const createThresholdAlertType = (createOptions: CreateRuleOptions) => { - const { - experimentalFeatures, - lists, - logger, - mergeStrategy, - ignoreFields, - ruleDataClient, - version, - ruleDataService, - } = createOptions; + const { experimentalFeatures, lists, logger, config, ruleDataClient, version, eventLogService } = + createOptions; const createSecurityRuleType = createSecurityRuleTypeFactory({ lists, logger, - mergeStrategy, - ignoreFields, + config, ruleDataClient, - ruleDataService, + eventLogService, }); return createSecurityRuleType({ id: THRESHOLD_RULE_TYPE_ID, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts index 6280a50d4981c..c94339da03b93 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -30,12 +30,12 @@ import { import { BaseHit } from '../../../../common/detection_engine/types'; import { ConfigType } from '../../../config'; import { SetupPlugins } from '../../../plugin'; -import { IRuleDataPluginService } from '../rule_execution_log/types'; import { RuleParams } from '../schemas/rule_schemas'; import { BuildRuleMessage } from '../signals/rule_messages'; import { AlertAttributes, BulkCreate, WrapHits, WrapSequences } from '../signals/types'; import { AlertsFieldMap, RulesFieldMap } from './field_maps'; import { ExperimentalFeatures } from '../../../../common/experimental_features'; +import { IEventLogService } from '../../../../../event_log/server'; export interface SecurityAlertTypeReturnValue { bulkCreateTimes: string[]; @@ -98,10 +98,9 @@ type SecurityAlertTypeWithExecutor< export type CreateSecurityRuleTypeFactory = (options: { lists: SetupPlugins['lists']; logger: Logger; - mergeStrategy: ConfigType['alertMergeStrategy']; - ignoreFields: ConfigType['alertIgnoreFields']; + config: ConfigType; ruleDataClient: IRuleDataClient; - ruleDataService: IRuleDataPluginService; + eventLogService: IEventLogService; }) => < TParams extends RuleParams & { index?: string[] | undefined }, TAlertInstanceContext extends AlertInstanceContext, @@ -127,10 +126,9 @@ export interface CreateRuleOptions { experimentalFeatures: ExperimentalFeatures; lists: SetupPlugins['lists']; logger: Logger; - mergeStrategy: ConfigType['alertMergeStrategy']; - ignoreFields: ConfigType['alertIgnoreFields']; + config: ConfigType; ml?: SetupPlugins['ml']; ruleDataClient: IRuleDataClient; version: string; - ruleDataService: IRuleDataPluginService; + eventLogService: IEventLogService; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/enable_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/enable_rule.ts index c6a5c00380242..2f3d05e0c9586 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/enable_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/enable_rule.ts @@ -44,6 +44,8 @@ export const enableRule = async ({ const currentStatusToDisable = ruleCurrentStatus[0]; await ruleStatusClient.update({ id: currentStatusToDisable.id, + ruleName: rule.name, + ruleType: rule.alertTypeId, attributes: { ...currentStatusToDisable.attributes, status: RuleExecutionStatus['going to run'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 850eee3993b60..207ea497c7e8e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -33,6 +33,7 @@ export const sampleRuleSO = (params: T): SavedObject { updated_at: '2020-03-27T22:55:59.577Z', attributes: { actions: [], + alertTypeId: 'siem.signals', enabled: true, name: 'rule-name', tags: ['some fake tag 1', 'some fake tag 2'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts index 5766390099e29..11145405dcc99 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts @@ -30,6 +30,7 @@ describe('threshold_executor', () => { updated_at: '2020-03-27T22:55:59.577Z', attributes: { actions: [], + alertTypeId: 'siem.signals', enabled: true, name: 'rule-name', tags: ['some fake tag 1', 'some fake tag 2'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 2696d6981083e..c2923b566175e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -32,10 +32,11 @@ import { mlExecutor } from './executors/ml'; import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { allowedExperimentalValues } from '../../../../common/experimental_features'; -import { ruleRegistryMocks } from '../../../../../rule_registry/server/mocks'; import { scheduleNotificationActions } from '../notifications/schedule_notification_actions'; import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client'; import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas'; +import { eventLogServiceMock } from '../../../../../event_log/server/mocks'; +import { createMockConfig } from '../routes/__mocks__'; jest.mock('./utils', () => { const original = jest.requireActual('./utils'); @@ -124,12 +125,12 @@ describe('signal_rule_alert_type', () => { let alert: ReturnType; let logger: ReturnType; let alertServices: AlertServicesMock; - let ruleDataService: ReturnType; + let eventLogService: ReturnType; beforeEach(() => { alertServices = alertsMock.createAlertServices(); logger = loggingSystemMock.createLogger(); - ruleDataService = ruleRegistryMocks.createRuleDataPluginService(); + eventLogService = eventLogServiceMock.create(); (getListsClient as jest.Mock).mockReturnValue({ listClient: getListClientMock(), exceptionsClient: getExceptionListClientMock(), @@ -194,9 +195,8 @@ describe('signal_rule_alert_type', () => { version, ml: mlMock, lists: listMock.createSetup(), - mergeStrategy: 'missingFields', - ignoreFields: [], - ruleDataService, + config: createMockConfig(), + eventLogService, }); mockRuleExecutionLogClient.logStatusChange.mockClear(); @@ -217,11 +217,18 @@ describe('signal_rule_alert_type', () => { payload.previousStartedAt = moment().subtract(100, 'm').toDate(); await alert.executor(payload); expect(logger.warn).toHaveBeenCalled(); - expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenLastCalledWith( + expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + newStatus: RuleExecutionStatus['going to run'], + }) + ); + expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenNthCalledWith( + 2, expect.objectContaining({ newStatus: RuleExecutionStatus.failed, metrics: { - gap: 'an hour', + executionGap: expect.any(Object), }, }) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 9a6c099ed1760..1e3a8a513c4a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -70,9 +70,9 @@ import { ConfigType } from '../../../config'; import { ExperimentalFeatures } from '../../../../common/experimental_features'; import { injectReferences, extractReferences } from './saved_object_references'; import { RuleExecutionLogClient } from '../rule_execution_log/rule_execution_log_client'; -import { IRuleDataPluginService } from '../rule_execution_log/types'; import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas'; import { scheduleThrottledNotificationActions } from '../notifications/schedule_throttle_notification_actions'; +import { IEventLogService } from '../../../../../event_log/server'; export const signalRulesAlertType = ({ logger, @@ -81,9 +81,8 @@ export const signalRulesAlertType = ({ version, ml, lists, - mergeStrategy, - ignoreFields, - ruleDataService, + config, + eventLogService, }: { logger: Logger; eventsTelemetry: TelemetryEventsSender | undefined; @@ -91,10 +90,10 @@ export const signalRulesAlertType = ({ version: string; ml: SetupPlugins['ml']; lists: SetupPlugins['lists'] | undefined; - mergeStrategy: ConfigType['alertMergeStrategy']; - ignoreFields: ConfigType['alertIgnoreFields']; - ruleDataService: IRuleDataPluginService; + config: ConfigType; + eventLogService: IEventLogService; }): SignalRuleAlertTypeDefinition => { + const { alertMergeStrategy: mergeStrategy, alertIgnoreFields: ignoreFields } = config; return { id: SIGNALS_ID, name: 'SIEM signal', @@ -138,14 +137,16 @@ export const signalRulesAlertType = ({ let hasError: boolean = false; let result = createSearchAfterReturnType(); const ruleStatusClient = new RuleExecutionLogClient({ - ruleDataService, + eventLogService, savedObjectsClient: services.savedObjectsClient, + underlyingClient: config.ruleExecutionLog.underlyingClient, }); const savedObject = await services.savedObjectsClient.get('alert', alertId); const { actions, name, + alertTypeId, schedule: { interval }, } = savedObject.attributes; const refresh = actions.length ? 'wait_for' : false; @@ -159,10 +160,16 @@ export const signalRulesAlertType = ({ logger.debug(buildRuleMessage('[+] Starting Signal Rule execution')); logger.debug(buildRuleMessage(`interval: ${interval}`)); let wroteWarningStatus = false; - await ruleStatusClient.logStatusChange({ + const basicLogArguments = { + spaceId, ruleId: alertId, + ruleName: name, + ruleType: alertTypeId, + }; + + await ruleStatusClient.logStatusChange({ + ...basicLogArguments, newStatus: RuleExecutionStatus['going to run'], - spaceId, }); // check if rule has permissions to access given index pattern @@ -194,8 +201,7 @@ export const signalRulesAlertType = ({ tryCatch( () => hasReadIndexPrivileges({ - spaceId, - ruleId: alertId, + ...basicLogArguments, privileges, logger, buildRuleMessage, @@ -207,13 +213,11 @@ export const signalRulesAlertType = ({ tryCatch( () => hasTimestampFields({ - spaceId, - ruleId: alertId, + ...basicLogArguments, wroteStatus: wroteStatus as boolean, timestampField: hasTimestampOverride ? (timestampOverride as string) : '@timestamp', - ruleName: name, timestampFieldCapsResponse: timestampFieldCaps, inputIndices, ruleStatusClient, @@ -247,11 +251,10 @@ export const signalRulesAlertType = ({ logger.warn(gapMessage); hasError = true; await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, + ...basicLogArguments, newStatus: RuleExecutionStatus.failed, message: gapMessage, - metrics: { gap: gapString }, + metrics: { executionGap: remainingGap }, }); } try { @@ -383,8 +386,7 @@ export const signalRulesAlertType = ({ if (result.warningMessages.length) { const warningMessage = buildRuleMessage(result.warningMessages.join()); await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, + ...basicLogArguments, newStatus: RuleExecutionStatus['partial failure'], message: warningMessage, }); @@ -445,13 +447,12 @@ export const signalRulesAlertType = ({ ); if (!hasError && !wroteWarningStatus && !result.warning) { await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, + ...basicLogArguments, newStatus: RuleExecutionStatus.succeeded, message: 'succeeded', metrics: { - bulkCreateTimeDurations: result.bulkCreateTimes, - searchAfterTimeDurations: result.searchAfterTimes, + indexingDurations: result.bulkCreateTimes, + searchDurations: result.searchAfterTimes, lastLookBackDate: result.lastLookBackDate?.toISOString(), }, }); @@ -474,13 +475,12 @@ export const signalRulesAlertType = ({ ); logger.error(errorMessage); await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, + ...basicLogArguments, newStatus: RuleExecutionStatus.failed, message: errorMessage, metrics: { - bulkCreateTimeDurations: result.bulkCreateTimes, - searchAfterTimeDurations: result.searchAfterTimes, + indexingDurations: result.bulkCreateTimes, + searchDurations: result.searchAfterTimes, lastLookBackDate: result.lastLookBackDate?.toISOString(), }, }); @@ -494,13 +494,12 @@ export const signalRulesAlertType = ({ logger.error(message); await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, + ...basicLogArguments, newStatus: RuleExecutionStatus.failed, message, metrics: { - bulkCreateTimeDurations: result.bulkCreateTimes, - searchAfterTimeDurations: result.searchAfterTimes, + indexingDurations: result.bulkCreateTimes, + searchDurations: result.searchAfterTimes, lastLookBackDate: result.lastLookBackDate?.toISOString(), }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index c1e7e23c3b161..82b4a46f482b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -255,6 +255,7 @@ export interface SignalHit { export interface AlertAttributes { actions: RuleAlertAction[]; + alertTypeId: string; enabled: boolean; name: string; tags: string[]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index c3e95d6d196ca..7d2eafa46d382 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -789,6 +789,7 @@ describe('utils', () => { inputIndices: ['myfa*'], ruleStatusClient, ruleId: 'ruleId', + ruleType: 'ruleType', spaceId: 'default', logger: mockLogger, buildRuleMessage, @@ -832,6 +833,7 @@ describe('utils', () => { inputIndices: ['myfa*'], ruleStatusClient, ruleId: 'ruleId', + ruleType: 'ruleType', spaceId: 'default', logger: mockLogger, buildRuleMessage, @@ -861,6 +863,7 @@ describe('utils', () => { inputIndices: ['logs-endpoint.alerts-*'], ruleStatusClient, ruleId: 'ruleId', + ruleType: 'ruleType', spaceId: 'default', logger: mockLogger, buildRuleMessage, @@ -890,6 +893,7 @@ describe('utils', () => { inputIndices: ['logs-endpoint.alerts-*'], ruleStatusClient, ruleId: 'ruleId', + ruleType: 'ruleType', spaceId: 'default', logger: mockLogger, buildRuleMessage, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 5993dd626729f..2aefc7ea0bd64 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -99,9 +99,20 @@ export const hasReadIndexPrivileges = async (args: { buildRuleMessage: BuildRuleMessage; ruleStatusClient: IRuleExecutionLogClient; ruleId: string; + ruleName: string; + ruleType: string; spaceId: string; }): Promise => { - const { privileges, logger, buildRuleMessage, ruleStatusClient, ruleId, spaceId } = args; + const { + privileges, + logger, + buildRuleMessage, + ruleStatusClient, + ruleId, + ruleName, + ruleType, + spaceId, + } = args; const indexNames = Object.keys(privileges.index); const [indexesWithReadPrivileges, indexesWithNoReadPrivileges] = partition( @@ -119,6 +130,8 @@ export const hasReadIndexPrivileges = async (args: { await ruleStatusClient.logStatusChange({ message: errorString, ruleId, + ruleName, + ruleType, spaceId, newStatus: RuleExecutionStatus['partial failure'], }); @@ -136,6 +149,8 @@ export const hasReadIndexPrivileges = async (args: { await ruleStatusClient.logStatusChange({ message: errorString, ruleId, + ruleName, + ruleType, spaceId, newStatus: RuleExecutionStatus['partial failure'], }); @@ -156,6 +171,7 @@ export const hasTimestampFields = async (args: { ruleStatusClient: IRuleExecutionLogClient; ruleId: string; spaceId: string; + ruleType: string; logger: Logger; buildRuleMessage: BuildRuleMessage; }): Promise => { @@ -167,6 +183,7 @@ export const hasTimestampFields = async (args: { inputIndices, ruleStatusClient, ruleId, + ruleType, spaceId, logger, buildRuleMessage, @@ -184,6 +201,8 @@ export const hasTimestampFields = async (args: { await ruleStatusClient.logStatusChange({ message: errorString.trimEnd(), ruleId, + ruleName, + ruleType, spaceId, newStatus: RuleExecutionStatus['partial failure'], }); @@ -210,6 +229,8 @@ export const hasTimestampFields = async (args: { await ruleStatusClient.logStatusChange({ message: errorString, ruleId, + ruleName, + ruleType, spaceId, newStatus: RuleExecutionStatus['partial failure'], }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts b/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts index 771e3e059c336..91f83d5e7cb37 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts @@ -7,12 +7,14 @@ export const TELEMETRY_MAX_BUFFER_SIZE = 100; -export const TELEMETRY_CHANNEL_LISTS = 'security-lists'; +export const TELEMETRY_CHANNEL_LISTS = 'security-lists-v2'; export const TELEMETRY_CHANNEL_ENDPOINT_META = 'endpoint-metadata'; -export const LIST_TRUSTED_APPLICATION = 'trusted_application'; +export const LIST_DETECTION_RULE_EXCEPTION = 'detection_rule_exception'; export const LIST_ENDPOINT_EXCEPTION = 'endpoint_exception'; export const LIST_ENDPOINT_EVENT_FILTER = 'endpoint_event_filter'; + +export const LIST_TRUSTED_APPLICATION = 'trusted_application'; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts index 61172fac511f7..a29f195ed5ecc 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts @@ -129,13 +129,12 @@ export const allowlistEventFields: AllowlistFields = { export const exceptionListEventFields: AllowlistFields = { created_at: true, - description: true, effectScope: true, entries: true, id: true, name: true, - os: true, os_types: true, + rule_version: true, }; /** @@ -143,7 +142,7 @@ export const exceptionListEventFields: AllowlistFields = { * * @param allowlist * @param event - * @returns + * @returns TelemetryEvent with explicitly required fields */ export function copyAllowlistedFields( allowlist: AllowlistFields, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts index 647219e8c5585..528082d8cb5b1 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts @@ -8,13 +8,14 @@ import moment from 'moment'; import { createMockPackagePolicy } from './mocks'; import { + LIST_DETECTION_RULE_EXCEPTION, LIST_ENDPOINT_EXCEPTION, LIST_ENDPOINT_EVENT_FILTER, LIST_TRUSTED_APPLICATION, } from './constants'; import { getPreviousDiagTaskTimestamp, - getPreviousEpMetaTaskTimestamp, + getPreviousDailyTaskTimestamp, batchTelemetryRecords, isPackagePolicyList, templateExceptionList, @@ -53,7 +54,7 @@ describe('test endpoint meta telemetry scheduled task timing helper', () => { test('test -24 hours is returned when there is no previous task run', async () => { const executeTo = moment().utc().toISOString(); const executeFrom = undefined; - const newExecuteFrom = getPreviousEpMetaTaskTimestamp(executeTo, executeFrom); + const newExecuteFrom = getPreviousDailyTaskTimestamp(executeTo, executeFrom); expect(newExecuteFrom).toEqual(moment(executeTo).subtract(24, 'hours').toISOString()); }); @@ -61,7 +62,7 @@ describe('test endpoint meta telemetry scheduled task timing helper', () => { test('test -24 hours is returned when there was a previous task run', async () => { const executeTo = moment().utc().toISOString(); const executeFrom = moment(executeTo).subtract(24, 'hours').toISOString(); - const newExecuteFrom = getPreviousEpMetaTaskTimestamp(executeTo, executeFrom); + const newExecuteFrom = getPreviousDailyTaskTimestamp(executeTo, executeFrom); expect(newExecuteFrom).toEqual(executeFrom); }); @@ -71,7 +72,7 @@ describe('test endpoint meta telemetry scheduled task timing helper', () => { test('test 24 hours is returned when previous task run took longer than 24 hours', async () => { const executeTo = moment().utc().toISOString(); const executeFrom = moment(executeTo).subtract(72, 'hours').toISOString(); // down 3 days - const newExecuteFrom = getPreviousEpMetaTaskTimestamp(executeTo, executeFrom); + const newExecuteFrom = getPreviousDailyTaskTimestamp(executeTo, executeFrom); expect(newExecuteFrom).toEqual(moment(executeTo).subtract(24, 'hours').toISOString()); }); @@ -134,61 +135,88 @@ describe('test package policy type guard', () => { }); describe('list telemetry schema', () => { + test('detection rules document is correctly formed', () => { + const data = [{ id: 'test_1' }] as ExceptionListItem[]; + const templatedItems = templateExceptionList(data, LIST_DETECTION_RULE_EXCEPTION); + + expect(templatedItems[0]?.detection_rule).not.toBeUndefined(); + expect(templatedItems[0]?.endpoint_exception).toBeUndefined(); + expect(templatedItems[0]?.endpoint_event_filter).toBeUndefined(); + expect(templatedItems[0]?.trusted_application).toBeUndefined(); + }); + + test('detection rules document is correctly formed with multiple entries', () => { + const data = [{ id: 'test_2' }, { id: 'test_2' }] as ExceptionListItem[]; + const templatedItems = templateExceptionList(data, LIST_DETECTION_RULE_EXCEPTION); + + expect(templatedItems[0]?.detection_rule).not.toBeUndefined(); + expect(templatedItems[1]?.detection_rule).not.toBeUndefined(); + expect(templatedItems[0]?.endpoint_exception).toBeUndefined(); + expect(templatedItems[0]?.endpoint_event_filter).toBeUndefined(); + expect(templatedItems[0]?.trusted_application).toBeUndefined(); + }); + test('trusted apps document is correctly formed', () => { const data = [{ id: 'test_1' }] as ExceptionListItem[]; const templatedItems = templateExceptionList(data, LIST_TRUSTED_APPLICATION); - expect(templatedItems[0]?.trusted_application.length).toEqual(1); - expect(templatedItems[0]?.endpoint_exception.length).toEqual(0); - expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(0); + expect(templatedItems[0]?.detection_rule).toBeUndefined(); + expect(templatedItems[0]?.endpoint_exception).toBeUndefined(); + expect(templatedItems[0]?.endpoint_event_filter).toBeUndefined(); + expect(templatedItems[0]?.trusted_application).not.toBeUndefined(); }); test('trusted apps document is correctly formed with multiple entries', () => { const data = [{ id: 'test_2' }, { id: 'test_2' }] as ExceptionListItem[]; const templatedItems = templateExceptionList(data, LIST_TRUSTED_APPLICATION); - expect(templatedItems[0]?.trusted_application.length).toEqual(1); - expect(templatedItems[1]?.trusted_application.length).toEqual(1); - expect(templatedItems[0]?.endpoint_exception.length).toEqual(0); - expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(0); + expect(templatedItems[0]?.detection_rule).toBeUndefined(); + expect(templatedItems[0]?.endpoint_exception).toBeUndefined(); + expect(templatedItems[0]?.endpoint_event_filter).toBeUndefined(); + expect(templatedItems[0]?.trusted_application).not.toBeUndefined(); + expect(templatedItems[1]?.trusted_application).not.toBeUndefined(); }); test('endpoint exception document is correctly formed', () => { const data = [{ id: 'test_3' }] as ExceptionListItem[]; const templatedItems = templateExceptionList(data, LIST_ENDPOINT_EXCEPTION); - expect(templatedItems[0]?.trusted_application.length).toEqual(0); - expect(templatedItems[0]?.endpoint_exception.length).toEqual(1); - expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(0); + expect(templatedItems[0]?.detection_rule).toBeUndefined(); + expect(templatedItems[0]?.endpoint_exception).not.toBeUndefined(); + expect(templatedItems[0]?.endpoint_event_filter).toBeUndefined(); + expect(templatedItems[0]?.trusted_application).toBeUndefined(); }); test('endpoint exception document is correctly formed with multiple entries', () => { const data = [{ id: 'test_4' }, { id: 'test_4' }, { id: 'test_4' }] as ExceptionListItem[]; const templatedItems = templateExceptionList(data, LIST_ENDPOINT_EXCEPTION); - expect(templatedItems[0]?.trusted_application.length).toEqual(0); - expect(templatedItems[0]?.endpoint_exception.length).toEqual(1); - expect(templatedItems[1]?.endpoint_exception.length).toEqual(1); - expect(templatedItems[2]?.endpoint_exception.length).toEqual(1); - expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(0); + expect(templatedItems[0]?.detection_rule).toBeUndefined(); + expect(templatedItems[0]?.endpoint_event_filter).toBeUndefined(); + expect(templatedItems[0]?.endpoint_exception).not.toBeUndefined(); + expect(templatedItems[1]?.endpoint_exception).not.toBeUndefined(); + expect(templatedItems[2]?.endpoint_exception).not.toBeUndefined(); + expect(templatedItems[0]?.trusted_application).toBeUndefined(); }); test('endpoint event filters document is correctly formed', () => { const data = [{ id: 'test_5' }] as ExceptionListItem[]; const templatedItems = templateExceptionList(data, LIST_ENDPOINT_EVENT_FILTER); - expect(templatedItems[0]?.trusted_application.length).toEqual(0); - expect(templatedItems[0]?.endpoint_exception.length).toEqual(0); - expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(1); + expect(templatedItems[0]?.detection_rule).toBeUndefined(); + expect(templatedItems[0]?.endpoint_event_filter).not.toBeUndefined(); + expect(templatedItems[0]?.endpoint_exception).toBeUndefined(); + expect(templatedItems[0]?.trusted_application).toBeUndefined(); }); test('endpoint event filters document is correctly formed with multiple entries', () => { const data = [{ id: 'test_6' }, { id: 'test_6' }] as ExceptionListItem[]; const templatedItems = templateExceptionList(data, LIST_ENDPOINT_EVENT_FILTER); - expect(templatedItems[0]?.trusted_application.length).toEqual(0); - expect(templatedItems[0]?.endpoint_exception.length).toEqual(0); - expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(1); - expect(templatedItems[1]?.endpoint_event_filter.length).toEqual(1); + expect(templatedItems[0]?.detection_rule).toBeUndefined(); + expect(templatedItems[0]?.endpoint_event_filter).not.toBeUndefined(); + expect(templatedItems[1]?.endpoint_event_filter).not.toBeUndefined(); + expect(templatedItems[0]?.endpoint_exception).toBeUndefined(); + expect(templatedItems[0]?.trusted_application).toBeUndefined(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts index a9eaef3ce6edc..e72b0ba7d16fe 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -11,6 +11,7 @@ import { PackagePolicy } from '../../../../fleet/common/types/models/package_pol import { copyAllowlistedFields, exceptionListEventFields } from './filters'; import { ExceptionListItem, ListTemplate, TelemetryEvent } from './types'; import { + LIST_DETECTION_RULE_EXCEPTION, LIST_ENDPOINT_EXCEPTION, LIST_ENDPOINT_EVENT_FILTER, LIST_TRUSTED_APPLICATION, @@ -46,7 +47,7 @@ export const getPreviousDiagTaskTimestamp = ( * @param lastExecutionTimestamp * @returns the timestamp to search from */ -export const getPreviousEpMetaTaskTimestamp = ( +export const getPreviousDailyTaskTimestamp = ( executeTo: string, lastExecutionTimestamp?: string ) => { @@ -97,18 +98,16 @@ export function isPackagePolicyList( * Maps trusted application to shared telemetry object * * @param exceptionListItem - * @returns collection of endpoint exceptions + * @returns collection of trusted applications */ export const trustedApplicationToTelemetryEntry = (trustedApplication: TrustedApp) => { return { id: trustedApplication.id, - version: trustedApplication.version || '', name: trustedApplication.name, - description: trustedApplication.description, created_at: trustedApplication.created_at, updated_at: trustedApplication.updated_at, entries: trustedApplication.entries, - os: trustedApplication.os, + os_types: [trustedApplication.os], } as ExceptionListItem; }; @@ -121,9 +120,29 @@ export const trustedApplicationToTelemetryEntry = (trustedApplication: TrustedAp export const exceptionListItemToTelemetryEntry = (exceptionListItem: ExceptionListItemSchema) => { return { id: exceptionListItem.id, - version: exceptionListItem._version || '', name: exceptionListItem.name, - description: exceptionListItem.description, + created_at: exceptionListItem.created_at, + updated_at: exceptionListItem.updated_at, + entries: exceptionListItem.entries, + os_types: exceptionListItem.os_types, + } as ExceptionListItem; +}; + +/** + * Maps detection rule exception list items to shared telemetry object + * + * @param exceptionListItem + * @param ruleVersion + * @returns collection of detection rule exceptions + */ +export const ruleExceptionListItemToTelemetryEvent = ( + exceptionListItem: ExceptionListItemSchema, + ruleVersion: number +) => { + return { + id: exceptionListItem.item_id, + name: exceptionListItem.description, + rule_version: ruleVersion, created_at: exceptionListItem.created_at, updated_at: exceptionListItem.updated_at, entries: exceptionListItem.entries, @@ -141,9 +160,7 @@ export const exceptionListItemToTelemetryEntry = (exceptionListItem: ExceptionLi export const templateExceptionList = (listData: ExceptionListItem[], listType: string) => { return listData.map((item) => { const template: ListTemplate = { - trusted_application: [], - endpoint_exception: [], - endpoint_event_filter: [], + '@timestamp': new Date().getTime(), }; // cast exception list type to a TelemetryEvent for allowlist filtering @@ -152,18 +169,23 @@ export const templateExceptionList = (listData: ExceptionListItem[], listType: s item as unknown as TelemetryEvent ); + if (listType === LIST_DETECTION_RULE_EXCEPTION) { + template.detection_rule = filteredListItem; + return template; + } + if (listType === LIST_TRUSTED_APPLICATION) { - template.trusted_application.push(filteredListItem); + template.trusted_application = filteredListItem; return template; } if (listType === LIST_ENDPOINT_EXCEPTION) { - template.endpoint_exception.push(filteredListItem); + template.endpoint_exception = filteredListItem; return template; } if (listType === LIST_ENDPOINT_EVENT_FILTER) { - template.endpoint_event_filter.push(filteredListItem); + template.endpoint_event_filter = filteredListItem; return template; } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts b/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts index 20a71657b2ffe..9168683141e48 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts @@ -8,7 +8,7 @@ // eslint-disable-next-line max-classes-per-file import { TelemetryEventsSender } from './sender'; import { TelemetryReceiver } from './receiver'; -import { DiagnosticTask, EndpointTask, ExceptionListsTask } from './tasks'; +import { DiagnosticTask, EndpointTask, ExceptionListsTask, DetectionRulesTask } from './tasks'; import { PackagePolicy } from '../../../../fleet/common/types/models/package_policy'; /** @@ -40,6 +40,8 @@ export const createMockTelemetryReceiver = (): jest.Mocked => fetchEndpointMetrics: jest.fn(), fetchEndpointPolicyResponses: jest.fn(), fetchTrustedApplications: jest.fn(), + fetchDetectionRules: jest.fn(), + fetchDetectionExceptionList: jest.fn(), } as unknown as jest.Mocked; }; @@ -79,3 +81,10 @@ export class MockTelemetryEndpointTask extends EndpointTask { export class MockExceptionListsTask extends ExceptionListsTask { public runTask = jest.fn(); } + +/** + * Creates a mocked Telemetry detection rules lists Task + */ +export class MockDetectionRuleListsTask extends DetectionRulesTask { + public runTask = jest.fn(); +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index 038b7687784f4..94aa6c867304f 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -17,8 +17,18 @@ import { AgentService, AgentPolicyServiceInterface } from '../../../../fleet/ser import { ExceptionListClient } from '../../../../lists/server'; import { EndpointAppContextService } from '../../endpoint/endpoint_app_context_services'; import { TELEMETRY_MAX_BUFFER_SIZE } from './constants'; -import { exceptionListItemToTelemetryEntry, trustedApplicationToTelemetryEntry } from './helpers'; -import { TelemetryEvent, ESLicense, ESClusterInfo, GetEndpointListResponse } from './types'; +import { + exceptionListItemToTelemetryEntry, + trustedApplicationToTelemetryEntry, + ruleExceptionListItemToTelemetryEvent, +} from './helpers'; +import { + TelemetryEvent, + ESLicense, + ESClusterInfo, + GetEndpointListResponse, + RuleSearchResult, +} from './types'; export class TelemetryReceiver { private readonly logger: Logger; @@ -27,6 +37,7 @@ export class TelemetryReceiver { private esClient?: ElasticsearchClient; private exceptionListClient?: ExceptionListClient; private soClient?: SavedObjectsClientContract; + private kibanaIndex?: string; private readonly max_records = 10_000; constructor(logger: Logger) { @@ -35,9 +46,11 @@ export class TelemetryReceiver { public async start( core?: CoreStart, + kibanaIndex?: string, endpointContextService?: EndpointAppContextService, exceptionListClient?: ExceptionListClient ) { + this.kibanaIndex = kibanaIndex; this.agentService = endpointContextService?.getAgentService(); this.agentPolicyService = endpointContextService?.getAgentPolicyService(); this.esClient = core?.elasticsearch.client.asInternalUser; @@ -240,6 +253,57 @@ export class TelemetryReceiver { }; } + public async fetchDetectionRules() { + if (this.esClient === undefined || this.esClient === null) { + throw Error('elasticsearch client is unavailable: cannot retrieve diagnostic alerts'); + } + + const query: SearchRequest = { + expand_wildcards: 'open,hidden', + index: `${this.kibanaIndex}*`, + ignore_unavailable: true, + size: this.max_records, + body: { + query: { + bool: { + filter: [ + { term: { 'alert.alertTypeId': 'siem.signals' } }, + { term: { 'alert.params.immutable': true } }, + ], + }, + }, + }, + }; + + return this.esClient.search(query); + } + + public async fetchDetectionExceptionList(listId: string, ruleVersion: number) { + if (this?.exceptionListClient === undefined || this?.exceptionListClient === null) { + throw Error('exception list client is unavailable: could not retrieve trusted applications'); + } + + // Ensure list is created if it does not exist + await this.exceptionListClient.createTrustedAppsList(); + + const results = await this.exceptionListClient?.findExceptionListsItem({ + listId: [listId], + filter: [], + perPage: this.max_records, + page: 1, + sortField: 'exception-list.created_at', + sortOrder: 'desc', + namespaceType: ['single'], + }); + + return { + data: results?.data.map((r) => ruleExceptionListItemToTelemetryEvent(r, ruleVersion)) ?? [], + total: results?.total ?? 0, + page: results?.page ?? 1, + per_page: results?.per_page ?? this.max_records, + }; + } + public async fetchClusterInfo(): Promise { if (this.esClient === undefined || this.esClient === null) { throw Error('elasticsearch client is unavailable: cannot retrieve cluster infomation'); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index 0037aaa28fee3..b0792ed7b4610 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -18,7 +18,7 @@ import { } from '../../../../task_manager/server'; import { TelemetryReceiver } from './receiver'; import { allowlistEventFields, copyAllowlistedFields } from './filters'; -import { DiagnosticTask, EndpointTask, ExceptionListsTask } from './tasks'; +import { DiagnosticTask, EndpointTask, ExceptionListsTask, DetectionRulesTask } from './tasks'; import { createUsageCounterLabel } from './helpers'; import { TelemetryEvent } from './types'; import { TELEMETRY_MAX_BUFFER_SIZE } from './constants'; @@ -42,6 +42,7 @@ export class TelemetryEventsSender { private diagnosticTask?: DiagnosticTask; private endpointTask?: EndpointTask; private exceptionListsTask?: ExceptionListsTask; + private detectionRulesTask?: DetectionRulesTask; constructor(logger: Logger) { this.logger = logger.get('telemetry_events'); @@ -59,6 +60,12 @@ export class TelemetryEventsSender { if (taskManager) { this.diagnosticTask = new DiagnosticTask(this.logger, taskManager, this, telemetryReceiver); this.endpointTask = new EndpointTask(this.logger, taskManager, this, telemetryReceiver); + this.detectionRulesTask = new DetectionRulesTask( + this.logger, + taskManager, + this, + telemetryReceiver + ); this.exceptionListsTask = new ExceptionListsTask( this.logger, taskManager, @@ -80,6 +87,7 @@ export class TelemetryEventsSender { this.logger.debug(`starting security telemetry tasks`); this.diagnosticTask.start(taskManager); this.endpointTask.start(taskManager); + this.detectionRulesTask?.start(taskManager); this.exceptionListsTask?.start(taskManager); } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.test.ts new file mode 100644 index 0000000000000..0a05afb8a6535 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.test.ts @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from 'src/core/server/mocks'; +import { taskManagerMock } from '../../../../../task_manager/server/mocks'; +import { TaskStatus } from '../../../../../task_manager/server'; +import { + TelemetryDetectionRulesTask, + TelemetryDetectionRuleListsTaskConstants, +} from './detection_rule'; +import { + createMockTelemetryEventsSender, + MockDetectionRuleListsTask, + createMockTelemetryReceiver, +} from '../mocks'; + +describe('test detection rule exception lists telemetry', () => { + let logger: ReturnType; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + }); + + describe('basic telemetry sanity checks', () => { + test('detection rule lists task can register', () => { + const telemetryDiagTask = new TelemetryDetectionRulesTask( + logger, + taskManagerMock.createSetup(), + createMockTelemetryEventsSender(true), + createMockTelemetryReceiver() + ); + + expect(telemetryDiagTask).toBeInstanceOf(TelemetryDetectionRulesTask); + }); + }); + + test('detection rule task should be registered', () => { + const mockTaskManager = taskManagerMock.createSetup(); + new TelemetryDetectionRulesTask( + logger, + mockTaskManager, + createMockTelemetryEventsSender(true), + createMockTelemetryReceiver() + ); + + expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalled(); + }); + + test('detection rule task should be scheduled', async () => { + const mockTaskManagerSetup = taskManagerMock.createSetup(); + const telemetryDiagTask = new TelemetryDetectionRulesTask( + logger, + mockTaskManagerSetup, + createMockTelemetryEventsSender(true), + createMockTelemetryReceiver() + ); + + const mockTaskManagerStart = taskManagerMock.createStart(); + await telemetryDiagTask.start(mockTaskManagerStart); + expect(mockTaskManagerStart.ensureScheduled).toHaveBeenCalled(); + }); + + test('detection rule task should run', async () => { + const mockContext = createMockTelemetryEventsSender(true); + const mockTaskManager = taskManagerMock.createSetup(); + const mockReceiver = createMockTelemetryReceiver(); + const telemetryDiagTask = new MockDetectionRuleListsTask( + logger, + mockTaskManager, + mockContext, + mockReceiver + ); + + const mockTaskInstance = { + id: TelemetryDetectionRuleListsTaskConstants.TYPE, + runAt: new Date(), + attempts: 0, + ownerId: '', + status: TaskStatus.Running, + startedAt: new Date(), + scheduledAt: new Date(), + retryAt: new Date(), + params: {}, + state: {}, + taskType: TelemetryDetectionRuleListsTaskConstants.TYPE, + }; + const createTaskRunner = + mockTaskManager.registerTaskDefinitions.mock.calls[0][0][ + TelemetryDetectionRuleListsTaskConstants.TYPE + ].createTaskRunner; + const taskRunner = createTaskRunner({ taskInstance: mockTaskInstance }); + await taskRunner.run(); + expect(telemetryDiagTask.runTask).toHaveBeenCalled(); + }); + + test('detection rule task should not query elastic if telemetry is not opted in', async () => { + const mockSender = createMockTelemetryEventsSender(false); + const mockTaskManager = taskManagerMock.createSetup(); + const mockReceiver = createMockTelemetryReceiver(); + new MockDetectionRuleListsTask(logger, mockTaskManager, mockSender, mockReceiver); + + const mockTaskInstance = { + id: TelemetryDetectionRuleListsTaskConstants.TYPE, + runAt: new Date(), + attempts: 0, + ownerId: '', + status: TaskStatus.Running, + startedAt: new Date(), + scheduledAt: new Date(), + retryAt: new Date(), + params: {}, + state: {}, + taskType: TelemetryDetectionRuleListsTaskConstants.TYPE, + }; + const createTaskRunner = + mockTaskManager.registerTaskDefinitions.mock.calls[0][0][ + TelemetryDetectionRuleListsTaskConstants.TYPE + ].createTaskRunner; + const taskRunner = createTaskRunner({ taskInstance: mockTaskInstance }); + await taskRunner.run(); + expect(mockReceiver.fetchDiagnosticAlerts).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts new file mode 100644 index 0000000000000..a362be187921d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { Logger } from 'src/core/server'; +import { + ConcreteTaskInstance, + TaskManagerSetupContract, + TaskManagerStartContract, +} from '../../../../../task_manager/server'; +import { LIST_DETECTION_RULE_EXCEPTION, TELEMETRY_CHANNEL_LISTS } from '../constants'; +import { batchTelemetryRecords, templateExceptionList } from '../helpers'; +import { TelemetryEventsSender } from '../sender'; +import { TelemetryReceiver } from '../receiver'; +import { ExceptionListItem, RuleSearchResult } from '../types'; + +export const TelemetryDetectionRuleListsTaskConstants = { + TIMEOUT: '10m', + TYPE: 'security:telemetry-detection-rules', + INTERVAL: '24h', + VERSION: '1.0.0', +}; + +const MAX_TELEMETRY_BATCH = 1_000; + +export class TelemetryDetectionRulesTask { + private readonly logger: Logger; + private readonly sender: TelemetryEventsSender; + private readonly receiver: TelemetryReceiver; + + constructor( + logger: Logger, + taskManager: TaskManagerSetupContract, + sender: TelemetryEventsSender, + receiver: TelemetryReceiver + ) { + this.logger = logger; + this.sender = sender; + this.receiver = receiver; + + taskManager.registerTaskDefinitions({ + [TelemetryDetectionRuleListsTaskConstants.TYPE]: { + title: 'Security Solution Detection Rule Lists Telemetry', + timeout: TelemetryDetectionRuleListsTaskConstants.TIMEOUT, + createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => { + const { state } = taskInstance; + + return { + run: async () => { + const taskExecutionTime = moment().utc().toISOString(); + const hits = await this.runTask(taskInstance.id); + + return { + state: { + lastExecutionTimestamp: taskExecutionTime, + runs: (state.runs || 0) + 1, + hits, + }, + }; + }, + cancel: async () => {}, + }; + }, + }, + }); + } + + public start = async (taskManager: TaskManagerStartContract) => { + try { + await taskManager.ensureScheduled({ + id: this.getTaskId(), + taskType: TelemetryDetectionRuleListsTaskConstants.TYPE, + scope: ['securitySolution'], + schedule: { + interval: TelemetryDetectionRuleListsTaskConstants.INTERVAL, + }, + state: { runs: 0 }, + params: { version: TelemetryDetectionRuleListsTaskConstants.VERSION }, + }); + } catch (e) { + this.logger.error(`Error scheduling task, received ${e.message}`); + } + }; + + private getTaskId = (): string => { + return `${TelemetryDetectionRuleListsTaskConstants.TYPE}:${TelemetryDetectionRuleListsTaskConstants.VERSION}`; + }; + + public runTask = async (taskId: string) => { + if (taskId !== this.getTaskId()) { + return 0; + } + + const isOptedIn = await this.sender.isTelemetryOptedIn(); + if (!isOptedIn) { + return 0; + } + + // Lists Telemetry: Detection Rules + + const { body: prebuiltRules } = await this.receiver.fetchDetectionRules(); + + const cacheArray = prebuiltRules.hits.hits.reduce((cache, searchHit) => { + const rule = searchHit._source as RuleSearchResult; + const ruleId = rule.alert.params.ruleId; + + const shouldNotProcess = + rule === null || + rule === undefined || + ruleId === null || + ruleId === undefined || + searchHit._source?.alert.params.exceptionsList.length === 0; + + if (shouldNotProcess) { + return cache; + } + + cache.push(rule); + return cache; + }, [] as RuleSearchResult[]); + + const detectionRuleExceptions = [] as ExceptionListItem[]; + for (const item of cacheArray) { + const ruleVersion = item.alert.params.version; + + for (const ex of item.alert.params.exceptionsList) { + const listItem = await this.receiver.fetchDetectionExceptionList(ex.list_id, ruleVersion); + for (const exceptionItem of listItem.data) { + detectionRuleExceptions.push(exceptionItem); + } + } + } + + const detectionRuleExceptionsJson = templateExceptionList( + detectionRuleExceptions, + LIST_DETECTION_RULE_EXCEPTION + ); + + batchTelemetryRecords(detectionRuleExceptionsJson, MAX_TELEMETRY_BATCH).forEach((batch) => { + this.sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); + }); + + return detectionRuleExceptions.length; + }; +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts index 0c066deea17d9..c6bf4b06e70f0 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts @@ -14,7 +14,7 @@ import { } from '../../../../../task_manager/server'; import { batchTelemetryRecords, - getPreviousEpMetaTaskTimestamp, + getPreviousDailyTaskTimestamp, isPackagePolicyList, } from '../helpers'; import { TelemetryEventsSender } from '../sender'; @@ -76,7 +76,7 @@ export class TelemetryEndpointTask { return { run: async () => { const taskExecutionTime = moment().utc().toISOString(); - const lastExecutionTimestamp = getPreviousEpMetaTaskTimestamp( + const lastExecutionTimestamp = getPreviousDailyTaskTimestamp( taskExecutionTime, taskInstance.state?.lastExecutionTimestamp ); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/index.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/index.ts index e090252b88d8f..a850f848567cb 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/index.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/index.ts @@ -8,3 +8,4 @@ export { TelemetryDiagTask as DiagnosticTask } from './diagnostic'; export { TelemetryEndpointTask as EndpointTask } from './endpoint'; export { TelemetryExceptionListsTask as ExceptionListsTask } from './security_lists'; +export { TelemetryDetectionRulesTask as DetectionRulesTask } from './detection_rule'; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts index abcad26ed000c..6aaf6f4371475 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts @@ -217,18 +217,45 @@ export interface GetEndpointListResponse { export interface ExceptionListItem { id: string; - version: string; + rule_version?: number; name: string; - description: string; created_at: string; updated_at: string; entries: object; - os: string; os_types: object; } export interface ListTemplate { - trusted_application: TelemetryEvent[]; - endpoint_exception: TelemetryEvent[]; - endpoint_event_filter: TelemetryEvent[]; + '@timestamp': number; + detection_rule?: TelemetryEvent; + endpoint_exception?: TelemetryEvent; + endpoint_event_filter?: TelemetryEvent; + trusted_application?: TelemetryEvent; +} + +// Detection Rule types + +interface ExceptionListEntry { + id: string; + list_id: string; + type: string; + namespace_type: string; +} + +interface DetectionRuleParms { + ruleId: string; + version: number; + type: string; + exceptionsList: ExceptionListEntry[]; +} + +export interface RuleSearchResult { + alert: { + name: string; + enabled: boolean; + tags: string[]; + createdAt: string; + updatedAt: string; + params: DetectionRuleParms; + }; } diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 9c4d739e0f434..f0a91f8b06c00 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -109,11 +109,14 @@ import { ctiFieldMap } from './lib/detection_engine/rule_types/field_maps/cti'; import { legacyRulesNotificationAlertType } from './lib/detection_engine/notifications/legacy_rules_notification_alert_type'; // eslint-disable-next-line no-restricted-imports import { legacyIsNotificationAlertExecutor } from './lib/detection_engine/notifications/legacy_types'; +import { IEventLogClientService, IEventLogService } from '../../event_log/server'; +import { registerEventLogProvider } from './lib/detection_engine/rule_execution_log/event_log_adapter/register_event_log_provider'; export interface SetupPlugins { alerting: AlertingSetup; data: DataPluginSetup; encryptedSavedObjects?: EncryptedSavedObjectsSetup; + eventLog: IEventLogService; features: FeaturesSetup; lists?: ListPluginSetup; ml?: MlSetup; @@ -121,20 +124,21 @@ export interface SetupPlugins { security?: SecuritySetup; spaces?: SpacesSetup; taskManager?: TaskManagerSetupContract; - usageCollection?: UsageCollectionSetup; telemetry?: TelemetryPluginSetup; + usageCollection?: UsageCollectionSetup; } export interface StartPlugins { alerting: AlertPluginStartContract; + cases?: CasesPluginStartContract; data: DataPluginStart; + eventLog: IEventLogClientService; fleet?: FleetStartContract; licensing: LicensingPluginStart; ruleRegistry: RuleRegistryPluginStartContract; + security: SecurityPluginStart; taskManager?: TaskManagerStartContract; telemetry?: TelemetryPluginStart; - security: SecurityPluginStart; - cases?: CasesPluginStartContract; } // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -202,6 +206,9 @@ export class Plugin implements IPlugin(); core.http.registerRouteHandlerContext( APP_ID, @@ -210,8 +217,9 @@ export class Plugin implements IPlugin plugins.spaces?.spacesService?.getSpaceId(request) || DEFAULT_SPACE_ID, getExecutionLogClient: () => new RuleExecutionLogClient({ - ruleDataService: plugins.ruleRegistry.ruleDataService, savedObjectsClient: context.core.savedObjects.client, + eventLogService, + underlyingClient: config.ruleExecutionLog.underlyingClient, }), }) ); @@ -262,11 +270,10 @@ export class Plugin implements IPlugin { return spacesFeatureDescription; }; + +export const DEFAULT_OBJECT_NOUN = i18n.translate('xpack.spaces.shareToSpace.objectNoun', { + defaultMessage: 'object', +}); diff --git a/x-pack/plugins/spaces/public/index.ts b/x-pack/plugins/spaces/public/index.ts index d13f8f48e6719..fe04358e30483 100644 --- a/x-pack/plugins/spaces/public/index.ts +++ b/x-pack/plugins/spaces/public/index.ts @@ -20,8 +20,9 @@ export type { CopyToSpaceSavedObjectTarget, } from './copy_saved_objects_to_space'; +export type { LegacyUrlConflictProps, EmbeddableLegacyUrlConflictProps } from './legacy_urls'; + export type { - LegacyUrlConflictProps, ShareToSpaceFlyoutProps, ShareToSpaceSavedObjectTarget, } from './share_saved_objects_to_space'; diff --git a/x-pack/plugins/spaces/public/legacy_urls/components/embeddable_legacy_url_conflict.tsx b/x-pack/plugins/spaces/public/legacy_urls/components/embeddable_legacy_url_conflict.tsx new file mode 100644 index 0000000000000..24f36723f9782 --- /dev/null +++ b/x-pack/plugins/spaces/public/legacy_urls/components/embeddable_legacy_url_conflict.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import type { EmbeddableLegacyUrlConflictProps } from '../types'; +import type { InternalProps } from './embeddable_legacy_url_conflict_internal'; + +export const getEmbeddableLegacyUrlConflict = async ( + internalProps: InternalProps +): Promise> => { + const { EmbeddableLegacyUrlConflictInternal } = await import( + './embeddable_legacy_url_conflict_internal' + ); + return (props: EmbeddableLegacyUrlConflictProps) => { + return ; + }; +}; diff --git a/x-pack/plugins/spaces/public/legacy_urls/components/embeddable_legacy_url_conflict_internal.tsx b/x-pack/plugins/spaces/public/legacy_urls/components/embeddable_legacy_url_conflict_internal.tsx new file mode 100644 index 0000000000000..8f86c2658cc3c --- /dev/null +++ b/x-pack/plugins/spaces/public/legacy_urls/components/embeddable_legacy_url_conflict_internal.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButtonEmpty, + EuiCallOut, + EuiCodeBlock, + EuiLink, + EuiSpacer, + EuiTextAlign, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import useAsync from 'react-use/lib/useAsync'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { StartServicesAccessor } from 'src/core/public'; + +import type { PluginsStart } from '../../plugin'; +import type { SpacesManager } from '../../spaces_manager'; +import type { EmbeddableLegacyUrlConflictProps } from '../types'; + +export interface InternalProps { + spacesManager: SpacesManager; + getStartServices: StartServicesAccessor; +} + +export const EmbeddableLegacyUrlConflictInternal = ( + props: InternalProps & EmbeddableLegacyUrlConflictProps +) => { + const { spacesManager, getStartServices, targetType, sourceId } = props; + + const [expandError, setExpandError] = useState(false); + + const { value: asyncParams } = useAsync(async () => { + const [{ docLinks }] = await getStartServices(); + const { id: targetSpace } = await spacesManager.getActiveSpace(); + const docLink = docLinks.links.spaces.kibanaDisableLegacyUrlAliasesApi; + const aliasJsonString = JSON.stringify({ targetSpace, targetType, sourceId }, null, 2); + return { docLink, aliasJsonString }; + }, [getStartServices, spacesManager]); + const { docLink, aliasJsonString } = asyncParams ?? {}; + + if (!aliasJsonString || !docLink) { + return null; + } + + return ( + <> + + + {expandError ? ( + + + {'_disable_legacy_url_aliases API'} + + ), + }} + /> + } + color="danger" + iconType="alert" + > + + {aliasJsonString} + + + + ) : ( + setExpandError(true)}> + {i18n.translate('xpack.spaces.embeddableLegacyUrlConflict.detailsButton', { + defaultMessage: `View details`, + })} + + )} + + ); +}; diff --git a/x-pack/plugins/spaces/public/lib/index.ts b/x-pack/plugins/spaces/public/legacy_urls/components/index.ts similarity index 63% rename from x-pack/plugins/spaces/public/lib/index.ts rename to x-pack/plugins/spaces/public/legacy_urls/components/index.ts index 8ba38e2ecefdd..c23749da2e895 100644 --- a/x-pack/plugins/spaces/public/lib/index.ts +++ b/x-pack/plugins/spaces/public/legacy_urls/components/index.ts @@ -5,4 +5,5 @@ * 2.0. */ -export { DocumentationLinksService } from './documentation_links'; +export { getEmbeddableLegacyUrlConflict } from './embeddable_legacy_url_conflict'; +export { getLegacyUrlConflict } from './legacy_url_conflict'; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/legacy_url_conflict.tsx b/x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict.tsx similarity index 100% rename from x-pack/plugins/spaces/public/share_saved_objects_to_space/components/legacy_url_conflict.tsx rename to x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict.tsx diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/legacy_url_conflict_internal.test.tsx b/x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict_internal.test.tsx similarity index 100% rename from x-pack/plugins/spaces/public/share_saved_objects_to_space/components/legacy_url_conflict_internal.test.tsx rename to x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict_internal.test.tsx diff --git a/x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict_internal.tsx b/x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict_internal.tsx new file mode 100644 index 0000000000000..a108e44fefe6e --- /dev/null +++ b/x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict_internal.tsx @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSpacer, + EuiToolTip, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import { first } from 'rxjs/operators'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { StartServicesAccessor } from 'src/core/public'; + +import { DEFAULT_OBJECT_NOUN } from '../../constants'; +import type { PluginsStart } from '../../plugin'; +import type { LegacyUrlConflictProps } from '../types'; + +export interface InternalProps { + getStartServices: StartServicesAccessor; +} + +export const LegacyUrlConflictInternal = (props: InternalProps & LegacyUrlConflictProps) => { + const { + getStartServices, + objectNoun = DEFAULT_OBJECT_NOUN, + currentObjectId, + otherObjectId, + otherObjectPath, + } = props; + + const [isDismissed, setIsDismissed] = useState(false); + + const { value: asyncParams } = useAsync(async () => { + const [{ application: applicationStart, docLinks }] = await getStartServices(); + const appId = await applicationStart.currentAppId$.pipe(first()).toPromise(); // retrieve the most recent value from the BehaviorSubject + const docLink = docLinks.links.spaces.kibanaLegacyUrlAliases; + return { applicationStart, appId, docLink }; + }, [getStartServices]); + const { docLink, applicationStart, appId } = asyncParams ?? {}; + + if (!applicationStart || !appId || !docLink || isDismissed) { + return null; + } + + function clickLinkButton() { + applicationStart!.navigateToApp(appId!, { path: otherObjectPath }); + } + + function clickDismissButton() { + setIsDismissed(true); + } + + return ( + + } + > + + {i18n.translate('xpack.spaces.legacyUrlConflict.documentationLinkText', { + defaultMessage: 'Learn more', + })} + + ), + }} + /> + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/constants.ts b/x-pack/plugins/spaces/public/legacy_urls/index.ts similarity index 51% rename from x-pack/plugins/spaces/public/share_saved_objects_to_space/components/constants.ts rename to x-pack/plugins/spaces/public/legacy_urls/index.ts index ef3248e1cd60a..b79f65075ce56 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/constants.ts +++ b/x-pack/plugins/spaces/public/legacy_urls/index.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; +export { getEmbeddableLegacyUrlConflict, getLegacyUrlConflict } from './components'; +export { createRedirectLegacyUrl } from './redirect_legacy_url'; -export const DEFAULT_OBJECT_NOUN = i18n.translate('xpack.spaces.shareToSpace.objectNoun', { - defaultMessage: 'object', -}); +export type { EmbeddableLegacyUrlConflictProps, LegacyUrlConflictProps } from './types'; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/utils/redirect_legacy_url.test.ts b/x-pack/plugins/spaces/public/legacy_urls/redirect_legacy_url.test.ts similarity index 100% rename from x-pack/plugins/spaces/public/share_saved_objects_to_space/utils/redirect_legacy_url.test.ts rename to x-pack/plugins/spaces/public/legacy_urls/redirect_legacy_url.test.ts diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/utils/redirect_legacy_url.ts b/x-pack/plugins/spaces/public/legacy_urls/redirect_legacy_url.ts similarity index 77% rename from x-pack/plugins/spaces/public/share_saved_objects_to_space/utils/redirect_legacy_url.ts rename to x-pack/plugins/spaces/public/legacy_urls/redirect_legacy_url.ts index d427b1bc05242..dbc3d68a4dde9 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/utils/redirect_legacy_url.ts +++ b/x-pack/plugins/spaces/public/legacy_urls/redirect_legacy_url.ts @@ -10,9 +10,9 @@ import { first } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import type { StartServicesAccessor } from 'src/core/public'; -import type { PluginsStart } from '../../plugin'; -import type { SpacesApiUi } from '../../ui_api'; -import { DEFAULT_OBJECT_NOUN } from '../components/constants'; +import { DEFAULT_OBJECT_NOUN } from '../constants'; +import type { PluginsStart } from '../plugin'; +import type { SpacesApiUi } from '../ui_api'; export function createRedirectLegacyUrl( getStartServices: StartServicesAccessor @@ -22,10 +22,10 @@ export function createRedirectLegacyUrl( const { currentAppId$, navigateToApp } = application; const appId = await currentAppId$.pipe(first()).toPromise(); // retrieve the most recent value from the BehaviorSubject - const title = i18n.translate('xpack.spaces.shareToSpace.redirectLegacyUrlToast.title', { + const title = i18n.translate('xpack.spaces.redirectLegacyUrlToast.title', { defaultMessage: `We redirected you to a new URL`, }); - const text = i18n.translate('xpack.spaces.shareToSpace.redirectLegacyUrlToast.text', { + const text = i18n.translate('xpack.spaces.redirectLegacyUrlToast.text', { defaultMessage: `The {objectNoun} you're looking for has a new location. Use this URL from now on.`, values: { objectNoun }, }); diff --git a/x-pack/plugins/spaces/public/legacy_urls/types.ts b/x-pack/plugins/spaces/public/legacy_urls/types.ts new file mode 100644 index 0000000000000..b3a80627b5b48 --- /dev/null +++ b/x-pack/plugins/spaces/public/legacy_urls/types.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Properties for the LegacyUrlConflict component. + */ +export interface LegacyUrlConflictProps { + /** + * The string that is used to describe the object in the callout, e.g., _There is a legacy URL for this page that points to a different + * **object**_. + * + * Default value is 'object'. + */ + objectNoun?: string; + /** + * The ID of the object that is currently shown on the page. + */ + currentObjectId: string; + /** + * The ID of the other object that the legacy URL alias points to. + */ + otherObjectId: string; + /** + * The path within your application to use for the new URL, optionally including `search` and/or `hash` URL components. Do not include + * `/app/my-app` or the current base path. + */ + otherObjectPath: string; +} + +/** + * Properties for the EmbeddableLegacyUrlConflict component. + */ +export interface EmbeddableLegacyUrlConflictProps { + /** + * The target type of the legacy URL alias. + */ + targetType: string; + /** + * The source ID of the legacy URL alias. + */ + sourceId: string; +} diff --git a/x-pack/plugins/spaces/public/lib/documentation_links.test.ts b/x-pack/plugins/spaces/public/lib/documentation_links.test.ts deleted file mode 100644 index 5ebaf0ebadaf6..0000000000000 --- a/x-pack/plugins/spaces/public/lib/documentation_links.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { docLinksServiceMock } from 'src/core/public/mocks'; - -import { DocumentationLinksService } from './documentation_links'; - -describe('DocumentationLinksService', () => { - const setup = () => { - const docLinks = docLinksServiceMock.createStartContract(); - const service = new DocumentationLinksService(docLinks); - return { docLinks, service }; - }; - - describe('#getKibanaPrivilegesDocUrl', () => { - it('returns expected value', () => { - const { service } = setup(); - expect(service.getKibanaPrivilegesDocUrl()).toMatchInlineSnapshot( - `"https://www.elastic.co/guide/en/kibana/mocked-test-branch/kibana-privileges.html"` - ); - }); - }); -}); diff --git a/x-pack/plugins/spaces/public/lib/documentation_links.ts b/x-pack/plugins/spaces/public/lib/documentation_links.ts deleted file mode 100644 index 108be17fe84ba..0000000000000 --- a/x-pack/plugins/spaces/public/lib/documentation_links.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { DocLinksStart } from 'src/core/public'; - -export class DocumentationLinksService { - private readonly kbnPrivileges: string; - - constructor(docLinks: DocLinksStart) { - this.kbnPrivileges = `${docLinks.links.security.kibanaPrivileges}`; - } - - public getKibanaPrivilegesDocUrl() { - return `${this.kbnPrivileges}`; - } -} diff --git a/x-pack/plugins/spaces/public/mocks.ts b/x-pack/plugins/spaces/public/mocks.ts index 76cafd4c7f5ae..9146a0aa2c99b 100644 --- a/x-pack/plugins/spaces/public/mocks.ts +++ b/x-pack/plugins/spaces/public/mocks.ts @@ -41,7 +41,7 @@ const createApiUiComponentsMock = () => { getSpaceList: jest.fn(), getLegacyUrlConflict: jest.fn(), getSpaceAvatar: jest.fn(), - getSavedObjectConflictMessage: jest.fn(), + getEmbeddableLegacyUrlConflict: jest.fn(), }; return mock; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/get_saved_object_conflict_message.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/get_saved_object_conflict_message.tsx deleted file mode 100644 index 66b2a5652057a..0000000000000 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/get_saved_object_conflict_message.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import type { SavedObjectConflictMessageProps } from '../types'; - -export const getSavedObjectConflictMessage = async (): Promise< - React.FC -> => { - const { SavedObjectConflictMessage } = await import('./saved_object_conflict_message'); - return (props: SavedObjectConflictMessageProps) => { - return ; - }; -}; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/index.ts b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/index.ts index fa641d03fd715..f692715789680 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/index.ts +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/index.ts @@ -6,5 +6,3 @@ */ export { getShareToSpaceFlyoutComponent } from './share_to_space_flyout'; -export { getSavedObjectConflictMessage } from './get_saved_object_conflict_message'; -export { getLegacyUrlConflict } from './legacy_url_conflict'; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/legacy_url_conflict_internal.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/legacy_url_conflict_internal.tsx deleted file mode 100644 index 95bf7b404db34..0000000000000 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/legacy_url_conflict_internal.tsx +++ /dev/null @@ -1,116 +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 { - EuiButton, - EuiButtonEmpty, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, -} from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; -import { first } from 'rxjs/operators'; - -import { FormattedMessage } from '@kbn/i18n/react'; -import type { ApplicationStart, StartServicesAccessor } from 'src/core/public'; - -import type { PluginsStart } from '../../plugin'; -import type { LegacyUrlConflictProps } from '../types'; -import { DEFAULT_OBJECT_NOUN } from './constants'; - -export interface InternalProps { - getStartServices: StartServicesAccessor; -} - -export const LegacyUrlConflictInternal = (props: InternalProps & LegacyUrlConflictProps) => { - const { - getStartServices, - objectNoun = DEFAULT_OBJECT_NOUN, - currentObjectId, - otherObjectId, - otherObjectPath, - } = props; - - const [applicationStart, setApplicationStart] = useState(); - const [isDismissed, setIsDismissed] = useState(false); - const [appId, setAppId] = useState(); - - useEffect(() => { - async function setup() { - const [{ application }] = await getStartServices(); - const appIdValue = await application.currentAppId$.pipe(first()).toPromise(); // retrieve the most recent value from the BehaviorSubject - setApplicationStart(application); - setAppId(appIdValue); - } - setup(); - }, [getStartServices]); - - if (!applicationStart || !appId || isDismissed) { - return null; - } - - function clickLinkButton() { - applicationStart!.navigateToApp(appId!, { path: otherObjectPath }); - } - - function clickDismissButton() { - setIsDismissed(true); - } - - return ( - - } - > - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/saved_object_conflict_message.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/saved_object_conflict_message.tsx deleted file mode 100644 index 22a1ad7cd20aa..0000000000000 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/saved_object_conflict_message.tsx +++ /dev/null @@ -1,56 +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 { EuiButtonEmpty, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; -import React, { useState } from 'react'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import type { SavedObjectConflictMessageProps } from '../types'; - -export const SavedObjectConflictMessage = ({ json }: SavedObjectConflictMessageProps) => { - const [expandError, setExpandError] = useState(false); - return ( - <> - - {i18n.translate('xpack.spaces.legacyURLConflict.documentationLinkText', { - defaultMessage: 'legacy URL alias', - })} - - ), - }} - /> - - {expandError ? ( - - ) : ( - setExpandError(true)}> - {i18n.translate('xpack.spaces.legacyURLConflict.expandError', { - defaultMessage: `Show more`, - })} - - )} - - ); -}; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/selectable_spaces_control.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/selectable_spaces_control.tsx index 3b7569d7c36da..9ba2e41098fdb 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/selectable_spaces_control.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/selectable_spaces_control.tsx @@ -26,7 +26,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common'; import { ALL_SPACES_ID, UNKNOWN_SPACE } from '../../../common/constants'; -import { DocumentationLinksService } from '../../lib'; import { getSpaceAvatarComponent } from '../../space_avatar'; import { useSpaces } from '../../spaces_context'; import type { SpacesDataEntry } from '../../types'; @@ -135,9 +134,7 @@ export const SelectableSpacesControl = (props: Props) => { return null; } - const kibanaPrivilegesUrl = new DocumentationLinksService( - docLinks! - ).getKibanaPrivilegesDocUrl(); + const docLink = docLinks?.links.security.kibanaPrivileges; return ( @@ -146,7 +143,7 @@ export const SelectableSpacesControl = (props: Props) => { defaultMessage="To view hidden spaces, you need {additionalPrivilegesLink}." values={{ additionalPrivilegesLink: ( - + { return null; } - const kibanaPrivilegesUrl = new DocumentationLinksService( - docLinks! - ).getKibanaPrivilegesDocUrl(); - + const docLink = docLinks?.links.security.kibanaPrivileges; return ( <> { values={{ objectNoun, readAndWritePrivilegesLink: ( - + + getEmbeddableLegacyUrlConflict({ spacesManager, getStartServices }) + ), getLegacyUrlConflict: wrapLazy(() => getLegacyUrlConflict({ getStartServices })), getSpaceAvatar: wrapLazy(getSpaceAvatarComponent), - getSavedObjectConflictMessage: wrapLazy(() => getSavedObjectConflictMessage()), }; }; diff --git a/x-pack/plugins/spaces/public/ui_api/index.ts b/x-pack/plugins/spaces/public/ui_api/index.ts index e0749b04de139..a8c77dce6fdff 100644 --- a/x-pack/plugins/spaces/public/ui_api/index.ts +++ b/x-pack/plugins/spaces/public/ui_api/index.ts @@ -7,8 +7,8 @@ import type { StartServicesAccessor } from 'src/core/public'; +import { createRedirectLegacyUrl } from '../legacy_urls'; import type { PluginsStart } from '../plugin'; -import { createRedirectLegacyUrl } from '../share_saved_objects_to_space'; import { useSpaces } from '../spaces_context'; import type { SpacesManager } from '../spaces_manager'; import { getComponents } from './components'; diff --git a/x-pack/plugins/spaces/public/ui_api/types.ts b/x-pack/plugins/spaces/public/ui_api/types.ts index 67e43f0cd31a6..eb2aefd5dd534 100644 --- a/x-pack/plugins/spaces/public/ui_api/types.ts +++ b/x-pack/plugins/spaces/public/ui_api/types.ts @@ -10,11 +10,8 @@ import type { ReactElement } from 'react'; import type { CoreStart } from 'src/core/public'; import type { CopyToSpaceFlyoutProps } from '../copy_saved_objects_to_space'; -import type { - LegacyUrlConflictProps, - SavedObjectConflictMessageProps, - ShareToSpaceFlyoutProps, -} from '../share_saved_objects_to_space'; +import type { EmbeddableLegacyUrlConflictProps, LegacyUrlConflictProps } from '../legacy_urls'; +import type { ShareToSpaceFlyoutProps } from '../share_saved_objects_to_space'; import type { SpaceAvatarProps } from '../space_avatar'; import type { SpaceListProps } from '../space_list'; import type { SpacesContextProps, SpacesReactContextValue } from '../spaces_context'; @@ -88,6 +85,12 @@ export interface SpacesApiUiComponent { * Note: must be rendered inside of a SpacesContext. */ getSpaceList: LazyComponentFn; + /** + * Displays a callout that needs to be used if an embeddable component call to `SavedObjectsClient.resolve()` results in an `"conflict"` + * outcome, which indicates that the user has loaded an embeddable which is associated directly with one object (A), *and* with a legacy + * URL that points to a different object (B). + */ + getEmbeddableLegacyUrlConflict: LazyComponentFn; /** * Displays a callout that needs to be used if a call to `SavedObjectsClient.resolve()` results in an `"conflict"` outcome, which * indicates that the user has loaded the page which is associated directly with one object (A), *and* with a legacy URL that points to a @@ -110,8 +113,4 @@ export interface SpacesApiUiComponent { * Displays an avatar for the given space. */ getSpaceAvatar: LazyComponentFn; - /** - * Displays a saved object conflict message that directs user to disable legacy URL alias - */ - getSavedObjectConflictMessage: LazyComponentFn; } diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx index 8c96aae2e0dae..bccd3aff72c58 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx @@ -20,7 +20,8 @@ jest.mock('../../../../../app/app_dependencies'); import { MlSharedContext } from '../../../../../app/__mocks__/shared_context'; import { getMlSharedImports } from '../../../../../shared_imports'; -describe('Transform: Transform List ', () => { +// FLAKY https://github.com/elastic/kibana/issues/112922 +describe.skip('Transform: Transform List ', () => { // Set timezone to US/Eastern for consistent test results. beforeEach(() => { moment.tz.setDefault('US/Eastern'); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7db9f3635b722..d5bae33bbc12c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -238,6 +238,8 @@ "xpack.lens.editorFrame.expressionMissingDatasource": "ビジュアライゼーションのデータソースが見つかりませんでした", "xpack.lens.editorFrame.expressionMissingVisualizationType": "ビジュアライゼーションタイプが見つかりません。", "xpack.lens.editorFrame.goToForums": "リクエストとフィードバック", + "xpack.lens.editorFrame.indexPatternNotFound": "インデックスパターンが見つかりませんでした", + "xpack.lens.editorFrame.indexPatternReconfigure": "インデックスパターン管理ページで再作成", "xpack.lens.editorFrame.invisibleIndicatorLabel": "このディメンションは現在グラフに表示されません", "xpack.lens.editorFrame.networkErrorMessage": "ネットワークエラーです。しばらくたってから再試行するか、管理者に連絡してください。", "xpack.lens.editorFrame.noColorIndicatorLabel": "このディメンションには個別の色がありません", @@ -364,6 +366,7 @@ "xpack.lens.indexPattern.cardinality": "ユニークカウント", "xpack.lens.indexPattern.cardinality.signature": "フィールド:文字列", "xpack.lens.indexPattern.cardinalityOf": "{name} のユニークカウント", + "xpack.lens.indexPattern.changeIndexPatternTitle": "インデックスパターン", "xpack.lens.indexPattern.chooseField": "フィールドを選択", "xpack.lens.indexPattern.chooseFieldLabel": "この関数を使用するには、フィールドを選択してください。", "xpack.lens.indexPattern.chooseSubFunction": "サブ関数を選択", @@ -403,6 +406,7 @@ "xpack.lens.indexPattern.derivative": "差異", "xpack.lens.indexPattern.derivativeOf": "{name} の差異", "xpack.lens.indexPattern.differences.signature": "メトリック:数値", + "xpack.lens.indexPattern.editFieldLabel": "インデックスパターンフィールドを編集", "xpack.lens.indexPattern.emptyDimensionButton": "空のディメンション", "xpack.lens.indexPattern.emptyFieldsLabel": "空のフィールド", "xpack.lens.indexPattern.emptyFieldsLabelHelp": "空のフィールドには、フィルターに基づく最初の 500 件のドキュメントの値が含まれていませんでした。", @@ -463,12 +467,15 @@ "xpack.lens.indexPattern.functionsLabel": "関数を選択", "xpack.lens.indexPattern.groupByDropdown": "グループ分けの条件", "xpack.lens.indexPattern.incompleteOperation": "(未完了)", + "xpack.lens.indexPattern.indexPatternLoadError": "インデックスパターンの読み込み中にエラーが発生", "xpack.lens.indexPattern.intervals": "間隔", + "xpack.lens.indexPattern.invalidFieldLabel": "無効なフィールドです。インデックスパターンを確認するか、別のフィールドを選択してください。", "xpack.lens.indexPattern.invalidInterval": "無効な間隔値", "xpack.lens.indexPattern.invalidOperationLabel": "選択した関数はこのフィールドで動作しません。", "xpack.lens.indexPattern.invalidReferenceConfiguration": "ディメンション\"{dimensionLabel}\"の構成が正しくありません", "xpack.lens.indexPattern.invalidTimeShift": "無効な時間シフトです。正の整数の後に単位s、m、h、d、w、M、yのいずれかを入力します。例:3時間は3hです", "xpack.lens.indexPattern.lastValue": "最終値", + "xpack.lens.indexPattern.lastValue.disabled": "この関数には、インデックスの日付フィールドが必要です", "xpack.lens.indexPattern.lastValue.invalidTypeSortField": "フィールド {invalidField} は日付フィールドではないため、並べ替えで使用できません", "xpack.lens.indexPattern.lastValue.signature": "フィールド:文字列", "xpack.lens.indexPattern.lastValue.sortField": "日付フィールドで並べ替え", @@ -504,6 +511,8 @@ "xpack.lens.indexPattern.movingAverage.windowLimitations": "ウィンドウには現在の値が含まれません。", "xpack.lens.indexPattern.movingAverageOf": "{name} の移動平均", "xpack.lens.indexPattern.multipleDateHistogramsError": "\"{dimensionLabel}\"は唯一の日付ヒストグラムではありません。時間シフトを使用するときには、1つの日付ヒストグラムのみを使用していることを確認してください。", + "xpack.lens.indexPattern.noPatternsDescription": "インデックスパターンを作成するか、別のデータソースに切り替えてください", + "xpack.lens.indexPattern.noPatternsLabel": "インデックスパターンがありません", "xpack.lens.indexPattern.numberFormatLabel": "数字", "xpack.lens.indexPattern.ofDocumentsLabel": "ドキュメント", "xpack.lens.indexPattern.otherDocsLabel": "その他", @@ -547,6 +556,7 @@ "xpack.lens.indexPattern.referenceFunctionPlaceholder": "サブ関数", "xpack.lens.indexPattern.removeColumnAriaLabel": "フィールドを追加するか、{groupLabel}までドラッグアンドドロップします", "xpack.lens.indexPattern.removeColumnLabel": "「{groupLabel}」から構成を削除", + "xpack.lens.indexPattern.removeFieldLabel": "インデックスパターンを削除", "xpack.lens.indexPattern.sortField.invalid": "無効なフィールドです。インデックスパターンを確認するか、別のフィールドを選択してください。", "xpack.lens.indexpattern.suggestions.nestingChangeLabel": "各 {outerOperation} の {innerOperation}", "xpack.lens.indexpattern.suggestions.overallLabel": "全体の {operation}", @@ -592,8 +602,12 @@ "xpack.lens.indexPattern.timeShiftSmallWarning": "{label}は{columnTimeShift}の時間シフトを使用しています。これは{interval}の日付ヒストグラム間隔よりも小さいです。不一致のデータを防止するには、時間シフトとして{interval}を使用します。", "xpack.lens.indexPattern.uniqueLabel": "{label} [{num}]", "xpack.lens.indexPattern.useAsTopLevelAgg": "最初にこのフィールドでグループ化", + "xpack.lens.indexPatterns.actionsPopoverLabel": "インデックスパターン設定", + "xpack.lens.indexPatterns.addFieldButton": "フィールドをインデックスパターンに追加", "xpack.lens.indexPatterns.clearFiltersLabel": "名前とタイプフィルターを消去", "xpack.lens.indexPatterns.fieldFiltersLabel": "タイプでフィルタリング", + "xpack.lens.indexPatterns.filterByNameLabel": "検索フィールド名", + "xpack.lens.indexPatterns.manageFieldButton": "インデックスパターンを管理", "xpack.lens.indexPatterns.noAvailableDataLabel": "データを含むフィールドはありません。", "xpack.lens.indexPatterns.noDataLabel": "フィールドがありません。", "xpack.lens.indexPatterns.noEmptyDataLabel": "空のフィールドがありません。", @@ -601,12 +615,14 @@ "xpack.lens.indexPatterns.noFields.fieldTypeFilterBullet": "別のフィールドフィルターを使用", "xpack.lens.indexPatterns.noFields.globalFiltersBullet": "グローバルフィルターを変更", "xpack.lens.indexPatterns.noFields.tryText": "試行対象:", + "xpack.lens.indexPatterns.noFieldsLabel": "このインデックスパターンにはフィールドがありません。", "xpack.lens.indexPatterns.noFilteredFieldsLabel": "選択したフィルターと一致するフィールドはありません。", "xpack.lens.indexPatterns.noMetaDataLabel": "メタフィールドがありません。", "xpack.lens.indexPatternSuggestion.removeLayerLabel": "{indexPatternTitle}のみを表示", "xpack.lens.indexPatternSuggestion.removeLayerPositionLabel": "レイヤー{layerNumber}のみを表示", "xpack.lens.labelInput.label": "ラベル", "xpack.lens.layerPanel.layerVisualizationType": "レイヤービジュアライゼーションタイプ", + "xpack.lens.layerPanel.missingIndexPattern": "インデックスパターンが見つかりませんでした", "xpack.lens.lensSavedObjectLabel": "レンズビジュアライゼーション", "xpack.lens.metric.addLayer": "ビジュアライゼーションレイヤーを追加", "xpack.lens.metric.groupLabel": "表形式の値と単一の値", @@ -4895,21 +4911,21 @@ "visTypeMetric.colorModes.backgroundOptionLabel": "背景", "visTypeMetric.colorModes.labelsOptionLabel": "ラベル", "visTypeMetric.colorModes.noneOptionLabel": "なし", - "visTypeMetric.function.adimension.splitGroup": "グループを分割", - "visTypeMetric.function.bgFill.help": "html 16 進数コード(#123456)、html 色(red、blue)、または rgba 値(rgba(255,255,255,1))。", - "visTypeMetric.function.bucket.help": "バケットディメンションの構成です。", - "visTypeMetric.function.colorMode.help": "色を変更するメトリックの部分", - "visTypeMetric.function.colorRange.help": "別の色が適用される値のグループを指定する範囲オブジェクト。", - "visTypeMetric.function.colorSchema.help": "使用する配色", - "visTypeMetric.function.dimension.metric": "メトリック", - "visTypeMetric.function.font.help": "フォント設定です。", - "visTypeMetric.function.help": "メトリックビジュアライゼーション", - "visTypeMetric.function.invertColors.help": "色範囲を反転します", - "visTypeMetric.function.metric.help": "メトリックディメンションの構成です。", - "visTypeMetric.function.percentageMode.help": "百分率モードでメトリックを表示します。colorRange を設定する必要があります。", - "visTypeMetric.function.showLabels.help": "メトリック値の下にラベルを表示します。", - "visTypeMetric.function.subText.help": "メトリックの下に表示するカスタムテキスト", - "visTypeMetric.function.useRanges.help": "有効な色範囲です。", + "expressionMetricVis.function.dimension.splitGroup": "グループを分割", + "expressionMetricVis.function.bgFill.help": "html 16 進数コード(#123456)、html 色(red、blue)、または rgba 値(rgba(255,255,255,1))。", + "expressionMetricVis.function.bucket.help": "バケットディメンションの構成です。", + "expressionMetricVis.function.colorMode.help": "色を変更するメトリックの部分", + "expressionMetricVis.function.colorRange.help": "別の色が適用される値のグループを指定する範囲オブジェクト。", + "expressionMetricVis.function.colorSchema.help": "使用する配色", + "expressionMetricVis.function.dimension.metric": "メトリック", + "expressionMetricVis.function.font.help": "フォント設定です。", + "expressionMetricVis.function.help": "メトリックビジュアライゼーション", + "expressionMetricVis.function.invertColors.help": "色範囲を反転します", + "expressionMetricVis.function.metric.help": "メトリックディメンションの構成です。", + "expressionMetricVis.function.percentageMode.help": "百分率モードでメトリックを表示します。colorRange を設定する必要があります。", + "expressionMetricVis.function.showLabels.help": "メトリック値の下にラベルを表示します。", + "expressionMetricVis.function.subText.help": "メトリックの下に表示するカスタムテキスト", + "expressionMetricVis.function.useRanges.help": "有効な色範囲です。", "visTypeMetric.metricDescription": "計算結果を単独の数字として表示します。", "visTypeMetric.metricTitle": "メトリック", "visTypeMetric.params.color.useForLabel": "使用する色", @@ -5191,6 +5207,12 @@ "visTypeTimeseries.indexPattern.timeRange.lastValue": "最終値", "visTypeTimeseries.indexPattern.timeRange.selectTimeRange": "選択してください", "visTypeTimeseries.indexPattern.сoarse": "粗い", + "visTypeTimeseries.indexPatternSelect.createIndexPatternText": "インデックスパターンを作成", + "visTypeTimeseries.indexPatternSelect.defaultIndexPatternText": "デフォルトのインデックスパターンが使用されています。", + "visTypeTimeseries.indexPatternSelect.label": "インデックスパターン", + "visTypeTimeseries.indexPatternSelect.queryAllIndexesText": "すべてのインデックスにクエリを実行するには * を使用します", + "visTypeTimeseries.indexPatternSelect.switchModePopover.areaLabel": "インデックスパターン選択モードを構成", + "visTypeTimeseries.indexPatternSelect.switchModePopover.title": "インデックスパターン選択モード", "visTypeTimeseries.kbnVisTypes.metricsDescription": "時系列データの高度な分析を実行します。", "visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB", "visTypeTimeseries.lastValueModeIndicator.lastBucketDate": "バケット:{lastBucketDate}", @@ -5319,6 +5341,7 @@ "visTypeTimeseries.seriesConfig.ignoreGlobalFilterLabel": "グローバルフィルターを無視しますか?", "visTypeTimeseries.seriesConfig.missingSeriesComponentDescription": "パネルタイプ {panelType} の数列コンポーネントが欠けています", "visTypeTimeseries.seriesConfig.offsetSeriesTimeLabel": "数列の時間を(1m, 1h, 1w, 1d)でオフセット", + "visTypeTimeseries.seriesConfig.overrideIndexPatternLabel": "インデックスパターンを上書きしますか?", "visTypeTimeseries.seriesConfig.templateHelpText": "eg. {templateExample}", "visTypeTimeseries.seriesConfig.templateLabel": "テンプレート", "visTypeTimeseries.sort.dragToSortAriaLabel": "ドラッグして並べ替えます", @@ -5449,6 +5472,7 @@ "visTypeTimeseries.timeseries.optionsTab.styleLabel": "スタイル", "visTypeTimeseries.timeseries.optionsTab.tooltipMode": "ツールチップ", "visTypeTimeseries.timeseries.optionsTab.truncateLegendLabel": "凡例を切り捨てますか?", + "visTypeTimeseries.timeSeries.overrideIndexPatternLabel": "インデックスパターンを上書きしますか?", "visTypeTimeseries.timeSeries.percentLabel": "パーセント", "visTypeTimeseries.timeseries.positionOptions.leftLabel": "左", "visTypeTimeseries.timeseries.positionOptions.rightLabel": "右", @@ -5513,8 +5537,9 @@ "visTypeTimeseries.visEditorVisualization.changesHaveNotBeenAppliedMessage": "ビジュアライゼーションへの変更が適用されました。", "visTypeTimeseries.visEditorVisualization.changesSuccessfullyAppliedMessage": "最新の変更が適用されました。", "visTypeTimeseries.visEditorVisualization.changesWillBeAutomaticallyAppliedMessage": "変更が自動的に適用されます。", - "visTypeTimeseries.visEditorVisualization.dataViewMode.dismissNoticeButtonText": "閉じる", - "visTypeTimeseries.visEditorVisualization.dataViewMode.link": "確認してください。", + "visTypeTimeseries.visEditorVisualization.indexPatternMode.dismissNoticeButtonText": "閉じる", + "visTypeTimeseries.visEditorVisualization.indexPatternMode.link": "確認してください。", + "visTypeTimeseries.visEditorVisualization.indexPatternMode.notificationTitle": "TSVBはインデックスパターンをサポートします", "visTypeTimeseries.visPicker.gaugeLabel": "ゲージ", "visTypeTimeseries.visPicker.metricLabel": "メトリック", "visTypeTimeseries.visPicker.tableLabel": "表", @@ -5888,6 +5913,7 @@ "visualizations.newVisWizard.newVisTypeTitle": "新規 {visTypeName}", "visualizations.newVisWizard.readDocumentationLink": "ドキュメンテーションを表示", "visualizations.newVisWizard.searchSelection.notFoundLabel": "一致インデックスまたは保存した検索が見つかりません。", + "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "インデックスパターン", "visualizations.newVisWizard.searchSelection.savedObjectType.search": "保存検索", "visualizations.newVisWizard.title": "新規ビジュアライゼーション", "visualizations.newVisWizard.toolsGroupTitle": "ツール", @@ -24181,10 +24207,6 @@ "xpack.spaces.shareToSpace.currentSpaceBadge": "現在", "xpack.spaces.shareToSpace.featureIsDisabledTooltip": "この機能はこのスペースでは無効です。", "xpack.spaces.shareToSpace.flyoutTitle": "{objectNoun}をスペースに割り当てる", - "xpack.spaces.shareToSpace.legacyUrlConflictBody": "現在、{objectNoun} [id={currentObjectId}]を表示しています。このページのレガシーURLは別の{objectNoun} [id={otherObjectId}]を示しています。", - "xpack.spaces.shareToSpace.legacyUrlConflictDismissButton": "閉じる", - "xpack.spaces.shareToSpace.legacyUrlConflictLinkButton": "他の{objectNoun}に移動", - "xpack.spaces.shareToSpace.legacyUrlConflictTitle": "2つのオブジェクトがこのURLに関連付けられています", "xpack.spaces.shareToSpace.noAvailableSpaces.canCreateNewSpace.linkText": "新しいスペースを作成", "xpack.spaces.shareToSpace.noAvailableSpaces.canCreateNewSpace.text": "オブジェクトを共有するには、{createANewSpaceLink}できます。", "xpack.spaces.shareToSpace.objectNoun": "オブジェクト", @@ -24193,8 +24215,6 @@ "xpack.spaces.shareToSpace.privilegeWarningBody": "この{objectNoun}のスペースを編集するには、すべてのスペースで{readAndWritePrivilegesLink}が必要です。", "xpack.spaces.shareToSpace.privilegeWarningLink": "読み書き権限", "xpack.spaces.shareToSpace.privilegeWarningTitle": "追加の権限が必要です", - "xpack.spaces.shareToSpace.redirectLegacyUrlToast.text": "検索している{objectNoun}は新しい場所にあります。今後はこのURLを使用してください。", - "xpack.spaces.shareToSpace.redirectLegacyUrlToast.title": "新しいURLに移動しました", "xpack.spaces.shareToSpace.saveButton": "保存して閉じる", "xpack.spaces.shareToSpace.shareErrorTitle": "{objectNoun}の更新エラー", "xpack.spaces.shareToSpace.shareModeControl.buttonGroupLegend": "この共有方法を選択", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 82bbbe84018e3..0bf8f7b5c6ce1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -241,8 +241,11 @@ "xpack.lens.editorFrame.expressionFailureMessage": "请求错误:{type},{reason}", "xpack.lens.editorFrame.expressionFailureMessageWithContext": "请求错误:{type},{context} 中的 {reason}", "xpack.lens.editorFrame.expressionMissingDatasource": "无法找到可视化的数据源", + "xpack.lens.editorFrame.expressionMissingIndexPattern": "找不到{count, plural, other {索引模式}}:{ids}", "xpack.lens.editorFrame.expressionMissingVisualizationType": "找不到可视化类型。", "xpack.lens.editorFrame.goToForums": "提出请求并提供反馈", + "xpack.lens.editorFrame.indexPatternNotFound": "未找到索引模式", + "xpack.lens.editorFrame.indexPatternReconfigure": "在索引模式管理页面中重新创建", "xpack.lens.editorFrame.invisibleIndicatorLabel": "此维度当前在图表中不可见", "xpack.lens.editorFrame.networkErrorMessage": "网络错误,请稍后重试或联系管理员。", "xpack.lens.editorFrame.noColorIndicatorLabel": "此维度没有单独的颜色", @@ -371,6 +374,7 @@ "xpack.lens.indexPattern.cardinality": "唯一计数", "xpack.lens.indexPattern.cardinality.signature": "field: string", "xpack.lens.indexPattern.cardinalityOf": "{name} 的唯一计数", + "xpack.lens.indexPattern.changeIndexPatternTitle": "索引模式", "xpack.lens.indexPattern.chooseField": "选择字段", "xpack.lens.indexPattern.chooseFieldLabel": "要使用此函数,请选择字段。", "xpack.lens.indexPattern.chooseSubFunction": "选择子函数", @@ -410,6 +414,7 @@ "xpack.lens.indexPattern.derivative": "差异", "xpack.lens.indexPattern.derivativeOf": "{name} 的差异", "xpack.lens.indexPattern.differences.signature": "指标:数字", + "xpack.lens.indexPattern.editFieldLabel": "编辑索引模式字段", "xpack.lens.indexPattern.emptyDimensionButton": "空维度", "xpack.lens.indexPattern.emptyFieldsLabel": "空字段", "xpack.lens.indexPattern.emptyFieldsLabelHelp": "空字段在基于您的筛选的前 500 个文档中不包含任何值。", @@ -471,12 +476,15 @@ "xpack.lens.indexPattern.functionsLabel": "选择函数", "xpack.lens.indexPattern.groupByDropdown": "分组依据", "xpack.lens.indexPattern.incompleteOperation": "(不完整)", + "xpack.lens.indexPattern.indexPatternLoadError": "加载索引模式时出错", "xpack.lens.indexPattern.intervals": "时间间隔", + "xpack.lens.indexPattern.invalidFieldLabel": "字段无效。检查索引模式或选取其他字段。", "xpack.lens.indexPattern.invalidInterval": "时间间隔值无效", "xpack.lens.indexPattern.invalidOperationLabel": "此字段不适用于选定函数。", "xpack.lens.indexPattern.invalidReferenceConfiguration": "维度“{dimensionLabel}”配置不正确", "xpack.lens.indexPattern.invalidTimeShift": "时间偏移无效。输入正整数数量,后跟以下单位之一:s、m、h、d、w、M、y。例如,3h 表示 3 小时", "xpack.lens.indexPattern.lastValue": "最后值", + "xpack.lens.indexPattern.lastValue.disabled": "此功能要求索引中存在日期字段", "xpack.lens.indexPattern.lastValue.invalidTypeSortField": "字段 {invalidField} 不是日期字段,不能用于排序", "xpack.lens.indexPattern.lastValue.signature": "field: string", "xpack.lens.indexPattern.lastValue.sortField": "按日期字段排序", @@ -496,6 +504,7 @@ "xpack.lens.indexPattern.min.description": "单值指标聚合,返回从聚合文档提取的数值中的最小值。", "xpack.lens.indexPattern.minOf": "{name} 的最小值", "xpack.lens.indexPattern.missingFieldLabel": "缺失字段", + "xpack.lens.indexPattern.missingIndexPattern": "找不到{count, plural, other {索引模式}} ({count, plural, other {id}}:{indexpatterns})", "xpack.lens.indexPattern.missingReferenceError": "“{dimensionLabel}”配置不完整", "xpack.lens.indexPattern.moveToWorkspace": "将 {field} 添加到工作区", "xpack.lens.indexPattern.moveToWorkspaceDisabled": "此字段无法自动添加到工作区。您仍可以在配置面板中直接使用它。", @@ -512,6 +521,8 @@ "xpack.lens.indexPattern.movingAverage.windowLimitations": "时间窗不包括当前值。", "xpack.lens.indexPattern.movingAverageOf": "{name} 的移动平均值", "xpack.lens.indexPattern.multipleDateHistogramsError": "“{dimensionLabel}”不是唯一的 Date Histogram。使用时间偏移时,请确保仅使用一个 Date Histogram。", + "xpack.lens.indexPattern.noPatternsDescription": "请创建索引模式或切换到其他数据源", + "xpack.lens.indexPattern.noPatternsLabel": "无索引模式", "xpack.lens.indexPattern.numberFormatLabel": "数字", "xpack.lens.indexPattern.ofDocumentsLabel": "文档", "xpack.lens.indexPattern.operationsNotFound": "未找到{operationLength, plural, other {运算}} {operationsList}", @@ -556,6 +567,7 @@ "xpack.lens.indexPattern.referenceFunctionPlaceholder": "子函数", "xpack.lens.indexPattern.removeColumnAriaLabel": "将字段添加或拖放到 {groupLabel}", "xpack.lens.indexPattern.removeColumnLabel": "从“{groupLabel}”中删除配置", + "xpack.lens.indexPattern.removeFieldLabel": "移除索引模式字段", "xpack.lens.indexPattern.sortField.invalid": "字段无效。检查索引模式或选取其他字段。", "xpack.lens.indexpattern.suggestions.nestingChangeLabel": "每个 {outerOperation} 的 {innerOperation}", "xpack.lens.indexpattern.suggestions.overallLabel": "总体 {operation}", @@ -601,9 +613,13 @@ "xpack.lens.indexPattern.timeShiftSmallWarning": "{label} 使用的时间偏移 {columnTimeShift} 小于 Date Histogram 时间间隔 {interval} 。要防止数据不匹配,请使用 {interval} 的倍数作为时间偏移。", "xpack.lens.indexPattern.uniqueLabel": "{label} [{num}]", "xpack.lens.indexPattern.useAsTopLevelAgg": "先按此字段分组", + "xpack.lens.indexPatterns.actionsPopoverLabel": "索引模式设置", + "xpack.lens.indexPatterns.addFieldButton": "将字段添加到索引模式", "xpack.lens.indexPatterns.clearFiltersLabel": "清除名称和类型筛选", "xpack.lens.indexPatterns.fieldFiltersLabel": "按类型筛选", "xpack.lens.indexPatterns.fieldSearchLiveRegion": "{availableFields} 个可用{availableFields, plural, other {字段}}。{emptyFields} 个空{emptyFields, plural, other {字段}}。{metaFields} 个元{metaFields, plural,other {字段}}。", + "xpack.lens.indexPatterns.filterByNameLabel": "搜索字段名称", + "xpack.lens.indexPatterns.manageFieldButton": "管理索引模式字段", "xpack.lens.indexPatterns.noAvailableDataLabel": "没有包含数据的可用字段。", "xpack.lens.indexPatterns.noDataLabel": "无字段。", "xpack.lens.indexPatterns.noEmptyDataLabel": "无空字段。", @@ -611,12 +627,14 @@ "xpack.lens.indexPatterns.noFields.fieldTypeFilterBullet": "使用不同的字段筛选", "xpack.lens.indexPatterns.noFields.globalFiltersBullet": "更改全局筛选", "xpack.lens.indexPatterns.noFields.tryText": "尝试:", + "xpack.lens.indexPatterns.noFieldsLabel": "在此索引模式中不存在任何字段。", "xpack.lens.indexPatterns.noFilteredFieldsLabel": "没有字段匹配选定筛选。", "xpack.lens.indexPatterns.noMetaDataLabel": "无元字段。", "xpack.lens.indexPatternSuggestion.removeLayerLabel": "仅显示 {indexPatternTitle}", "xpack.lens.indexPatternSuggestion.removeLayerPositionLabel": "仅显示图层 {layerNumber}", "xpack.lens.labelInput.label": "标签", "xpack.lens.layerPanel.layerVisualizationType": "图层可视化类型", + "xpack.lens.layerPanel.missingIndexPattern": "未找到索引模式", "xpack.lens.lensSavedObjectLabel": "Lens 可视化", "xpack.lens.metric.addLayer": "添加可视化图层", "xpack.lens.metric.groupLabel": "表和单值", @@ -4938,21 +4956,21 @@ "visTypeMetric.colorModes.backgroundOptionLabel": "背景", "visTypeMetric.colorModes.labelsOptionLabel": "标签", "visTypeMetric.colorModes.noneOptionLabel": "无", - "visTypeMetric.function.adimension.splitGroup": "拆分组", - "visTypeMetric.function.bgFill.help": "将颜色表示为 html 十六进制代码 (#123456)、html 颜色(red、blue)或 rgba 值 (rgba(255,255,255,1))。", - "visTypeMetric.function.bucket.help": "存储桶维度配置", - "visTypeMetric.function.colorMode.help": "指标的哪部分要上色", - "visTypeMetric.function.colorRange.help": "指定应将不同颜色应用到的值组的范围对象。", - "visTypeMetric.function.colorSchema.help": "要使用的颜色方案", - "visTypeMetric.function.dimension.metric": "指标", - "visTypeMetric.function.font.help": "字体设置。", - "visTypeMetric.function.help": "指标可视化", - "visTypeMetric.function.invertColors.help": "反转颜色范围", - "visTypeMetric.function.metric.help": "指标维度配置", - "visTypeMetric.function.percentageMode.help": "以百分比模式显示指标。需要设置 colorRange。", - "visTypeMetric.function.showLabels.help": "在指标值下显示标签。", - "visTypeMetric.function.subText.help": "要在指标下显示的定制文本", - "visTypeMetric.function.useRanges.help": "已启用颜色范围。", + "expressionMetricVis.function.dimension.splitGroup": "拆分组", + "expressionMetricVis.function.bgFill.help": "将颜色表示为 html 十六进制代码 (#123456)、html 颜色(red、blue)或 rgba 值 (rgba(255,255,255,1))。", + "expressionMetricVis.function.bucket.help": "存储桶维度配置", + "expressionMetricVis.function.colorMode.help": "指标的哪部分要上色", + "expressionMetricVis.function.colorRange.help": "指定应将不同颜色应用到的值组的范围对象。", + "expressionMetricVis.function.colorSchema.help": "要使用的颜色方案", + "expressionMetricVis.function.dimension.metric": "指标", + "expressionMetricVis.function.font.help": "字体设置。", + "expressionMetricVis.function.help": "指标可视化", + "expressionMetricVis.function.invertColors.help": "反转颜色范围", + "expressionMetricVis.function.metric.help": "指标维度配置", + "expressionMetricVis.function.percentageMode.help": "以百分比模式显示指标。需要设置 colorRange。", + "expressionMetricVis.function.showLabels.help": "在指标值下显示标签。", + "expressionMetricVis.function.subText.help": "要在指标下显示的定制文本", + "expressionMetricVis.function.useRanges.help": "已启用颜色范围。", "visTypeMetric.metricDescription": "将计算结果显示为单个数字。", "visTypeMetric.metricTitle": "指标", "visTypeMetric.params.color.useForLabel": "将颜色用于", @@ -5234,6 +5252,12 @@ "visTypeTimeseries.indexPattern.timeRange.lastValue": "最后值", "visTypeTimeseries.indexPattern.timeRange.selectTimeRange": "选择", "visTypeTimeseries.indexPattern.сoarse": "粗糙", + "visTypeTimeseries.indexPatternSelect.createIndexPatternText": "创建索引模式", + "visTypeTimeseries.indexPatternSelect.defaultIndexPatternText": "将使用默认索引模式。", + "visTypeTimeseries.indexPatternSelect.label": "索引模式", + "visTypeTimeseries.indexPatternSelect.queryAllIndexesText": "要查询所有索引,请使用 *", + "visTypeTimeseries.indexPatternSelect.switchModePopover.areaLabel": "配置索引模式选择模式", + "visTypeTimeseries.indexPatternSelect.switchModePopover.title": "索引模式选择模式", "visTypeTimeseries.kbnVisTypes.metricsDescription": "对时间序列数据执行高级分析。", "visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB", "visTypeTimeseries.lastValueModeIndicator.lastBucketDate": "存储桶:{lastBucketDate}", @@ -5363,6 +5387,7 @@ "visTypeTimeseries.seriesConfig.ignoreGlobalFilterLabel": "忽略全局筛选?", "visTypeTimeseries.seriesConfig.missingSeriesComponentDescription": "以下面板类型缺失序列组件:{panelType}", "visTypeTimeseries.seriesConfig.offsetSeriesTimeLabel": "将序列时间偏移(1m、1h、1w、1d)", + "visTypeTimeseries.seriesConfig.overrideIndexPatternLabel": "覆盖索引模式?", "visTypeTimeseries.seriesConfig.templateHelpText": "例如 {templateExample}", "visTypeTimeseries.seriesConfig.templateLabel": "模板", "visTypeTimeseries.sort.dragToSortAriaLabel": "拖动以排序", @@ -5493,6 +5518,7 @@ "visTypeTimeseries.timeseries.optionsTab.styleLabel": "样式", "visTypeTimeseries.timeseries.optionsTab.tooltipMode": "工具提示", "visTypeTimeseries.timeseries.optionsTab.truncateLegendLabel": "截断图例?", + "visTypeTimeseries.timeSeries.overrideIndexPatternLabel": "覆盖索引模式?", "visTypeTimeseries.timeSeries.percentLabel": "百分比", "visTypeTimeseries.timeseries.positionOptions.leftLabel": "左", "visTypeTimeseries.timeseries.positionOptions.rightLabel": "右", @@ -5557,8 +5583,9 @@ "visTypeTimeseries.visEditorVisualization.changesHaveNotBeenAppliedMessage": "尚未应用对此可视化的更改。", "visTypeTimeseries.visEditorVisualization.changesSuccessfullyAppliedMessage": "已应用最新更改。", "visTypeTimeseries.visEditorVisualization.changesWillBeAutomaticallyAppliedMessage": "将自动应用更改。", - "visTypeTimeseries.visEditorVisualization.dataViewMode.dismissNoticeButtonText": "关闭", - "visTypeTimeseries.visEditorVisualization.dataViewMode.link": "请查看。", + "visTypeTimeseries.visEditorVisualization.indexPatternMode.dismissNoticeButtonText": "关闭", + "visTypeTimeseries.visEditorVisualization.indexPatternMode.link": "请查看。", + "visTypeTimeseries.visEditorVisualization.indexPatternMode.notificationTitle": "TSVB 现在支持索引模式", "visTypeTimeseries.visPicker.gaugeLabel": "仪表盘", "visTypeTimeseries.visPicker.metricLabel": "指标", "visTypeTimeseries.visPicker.tableLabel": "表", @@ -5933,6 +5960,7 @@ "visualizations.newVisWizard.readDocumentationLink": "阅读文档", "visualizations.newVisWizard.resultsFound": "{resultCount, plural, other {类型}}已找到", "visualizations.newVisWizard.searchSelection.notFoundLabel": "未找到匹配的索引或已保存搜索。", + "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "索引模式", "visualizations.newVisWizard.searchSelection.savedObjectType.search": "已保存搜索", "visualizations.newVisWizard.title": "新建可视化", "visualizations.newVisWizard.toolsGroupTitle": "工具", @@ -24584,10 +24612,6 @@ "xpack.spaces.shareToSpace.currentSpaceBadge": "当前", "xpack.spaces.shareToSpace.featureIsDisabledTooltip": "此功能在此工作区中已禁用。", "xpack.spaces.shareToSpace.flyoutTitle": "将 {objectNoun} 分配给工作区", - "xpack.spaces.shareToSpace.legacyUrlConflictBody": "当前您正在查看 {objectNoun} [id={currentObjectId}]。此页面的旧 URL 显示不同的 {objectNoun} [id={otherObjectId}]。", - "xpack.spaces.shareToSpace.legacyUrlConflictDismissButton": "关闭", - "xpack.spaces.shareToSpace.legacyUrlConflictLinkButton": "前往其他 {objectNoun}", - "xpack.spaces.shareToSpace.legacyUrlConflictTitle": "2 个对象与此 URL 关联", "xpack.spaces.shareToSpace.noAvailableSpaces.canCreateNewSpace.linkText": "创建新工作区", "xpack.spaces.shareToSpace.noAvailableSpaces.canCreateNewSpace.text": "您可以{createANewSpaceLink},用于共享您的对象。", "xpack.spaces.shareToSpace.objectNoun": "对象", @@ -24596,8 +24620,6 @@ "xpack.spaces.shareToSpace.privilegeWarningBody": "要编辑此 {objectNoun} 的工作区,您在所有工作区中都需要{readAndWritePrivilegesLink}。", "xpack.spaces.shareToSpace.privilegeWarningLink": "读写权限", "xpack.spaces.shareToSpace.privilegeWarningTitle": "需要其他权限", - "xpack.spaces.shareToSpace.redirectLegacyUrlToast.text": "您正在寻找的{objectNoun}具有新的位置。从现在开始使用此 URL。", - "xpack.spaces.shareToSpace.redirectLegacyUrlToast.title": "我们已将您重定向到新 URL", "xpack.spaces.shareToSpace.relativesControl.description": "{relativesCount} 个相关{relativesCount, plural, other {对象}}也将更改。", "xpack.spaces.shareToSpace.saveButton": "保存并关闭", "xpack.spaces.shareToSpace.shareErrorTitle": "更新 {objectNoun} 时出错", diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx b/x-pack/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx index 01c7155832745..8176d3fcbbca2 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { of } from 'rxjs'; import { ComponentType } from 'enzyme'; import { LocationDescriptorObject } from 'history'; + import { docLinksServiceMock, uiSettingsServiceMock, @@ -17,6 +18,7 @@ import { scopedHistoryMock, } from '../../../../../../src/core/public/mocks'; import { AppContextProvider } from '../../../public/application/app_context'; +import { AppDeps } from '../../../public/application/app'; import { LicenseStatus } from '../../../common/types/license_status'; class MockTimeBuckets { @@ -35,7 +37,7 @@ history.createHref.mockImplementation((location: LocationDescriptorObject) => { return `${location.pathname}${location.search ? '?' + location.search : ''}`; }); -export const mockContextValue = { +export const mockContextValue: AppDeps = { licenseStatus$: of({ valid: true }), docLinks: docLinksServiceMock.createStartContract(), setBreadcrumbs: jest.fn(), diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/index.ts index 961e2a458dc0c..09a841ff147a4 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/index.ts @@ -11,7 +11,7 @@ import { setup as watchCreateJsonSetup } from './watch_create_json.helpers'; import { setup as watchCreateThresholdSetup } from './watch_create_threshold.helpers'; import { setup as watchEditSetup } from './watch_edit.helpers'; -export { nextTick, getRandomString, findTestSubject, TestBed } from '@kbn/test/jest'; +export { getRandomString, findTestSubject, TestBed } from '@kbn/test/jest'; export { wrapBodyResponse, unwrapBodyResponse } from './body_response'; export { setupEnvironment } from './setup_environment'; diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts index 05b325ee946bd..5ba0387d21ba7 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts @@ -7,6 +7,7 @@ import axios from 'axios'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; + import { init as initHttpRequests } from './http_requests'; import { setHttpClient, setSavedObjectsClient } from '../../../public/application/lib/api'; diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts index c70684b80a6d5..caddf1df93d40 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts @@ -93,7 +93,7 @@ export type TestSubjects = | 'toEmailAddressInput' | 'triggerIntervalSizeInput' | 'watchActionAccordion' - | 'watchActionAccordion.mockComboBox' + | 'watchActionAccordion.toEmailAddressInput' | 'watchActionsPanel' | 'watchThresholdButton' | 'watchThresholdInput' diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts index ad171f9e40cad..c0643e70dded9 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, findTestSubject, TestBed, TestBedConfig, nextTick } from '@kbn/test/jest'; +import { registerTestBed, findTestSubject, TestBed, TestBedConfig } from '@kbn/test/jest'; import { WatchList } from '../../../public/application/sections/watch_list/components/watch_list'; import { ROUTES, REFRESH_INTERVALS } from '../../../common/constants'; import { withAppContext } from './app_context.mock'; @@ -24,7 +24,6 @@ const initTestBed = registerTestBed(withAppContext(WatchList), testBedConfig); export interface WatchListTestBed extends TestBed { actions: { selectWatchAt: (index: number) => void; - clickWatchAt: (index: number) => void; clickWatchActionAt: (index: number, action: 'delete' | 'edit') => void; searchWatches: (term: string) => void; advanceTimeToTableRefresh: () => Promise; @@ -45,18 +44,6 @@ export const setup = async (): Promise => { checkBox.simulate('change', { target: { checked: true } }); }; - const clickWatchAt = async (index: number) => { - const { rows } = testBed.table.getMetaData('watchesTable'); - const watchesLink = findTestSubject(rows[index].reactWrapper, 'watchesLink'); - - await act(async () => { - const { href } = watchesLink.props(); - testBed.router.navigateTo(href!); - await nextTick(); - testBed.component.update(); - }); - }; - const clickWatchActionAt = async (index: number, action: 'delete' | 'edit') => { const { component, table } = testBed; const { rows } = table.getMetaData('watchesTable'); @@ -95,7 +82,6 @@ export const setup = async (): Promise => { ...testBed, actions: { selectWatchAt, - clickWatchAt, clickWatchActionAt, searchWatches, advanceTimeToTableRefresh, diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts index a1c7e8b404997..02b6908fc1d4c 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, findTestSubject, TestBed, TestBedConfig, delay } from '@kbn/test/jest'; +import { registerTestBed, findTestSubject, TestBed, TestBedConfig } from '@kbn/test/jest'; import { WatchStatus } from '../../../public/application/sections/watch_status/components/watch_status'; import { ROUTES } from '../../../common/constants'; import { WATCH_ID } from './jest_constants'; @@ -89,9 +89,8 @@ export const setup = async (): Promise => { await act(async () => { button.simulate('click'); - await delay(100); - component.update(); }); + component.update(); }; return { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts index 4a632d9752cac..f9ea51a80ae76 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts @@ -9,7 +9,7 @@ import { act } from 'react-dom/test-utils'; import { getExecuteDetails } from '../../__fixtures__'; import { defaultWatch } from '../../public/application/models/watch'; -import { setupEnvironment, pageHelpers, nextTick, wrapBodyResponse } from './helpers'; +import { setupEnvironment, pageHelpers, wrapBodyResponse } from './helpers'; import { WatchCreateJsonTestBed } from './helpers/watch_create_json.helpers'; import { WATCH } from './helpers/jest_constants'; @@ -19,19 +19,19 @@ describe(' create route', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchCreateJsonTestBed; + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); server.restore(); }); describe('on component mount', () => { beforeEach(async () => { testBed = await setup(); - - await act(async () => { - const { component } = testBed; - await nextTick(); - component.update(); - }); + testBed.component.update(); }); test('should set the correct page title', () => { @@ -92,7 +92,6 @@ describe(' create route', () => { await act(async () => { actions.clickSubmitButton(); - await nextTick(); }); const latestRequest = server.requests[server.requests.length - 1]; @@ -141,9 +140,8 @@ describe(' create route', () => { await act(async () => { actions.clickSubmitButton(); - await nextTick(); - component.update(); }); + component.update(); expect(exists('sectionError')).toBe(true); expect(find('sectionError').text()).toContain(error.message); @@ -169,7 +167,6 @@ describe(' create route', () => { await act(async () => { actions.clickSimulateButton(); - await nextTick(); }); const latestRequest = server.requests[server.requests.length - 1]; @@ -230,9 +227,8 @@ describe(' create route', () => { await act(async () => { actions.clickSimulateButton(); - await nextTick(); - component.update(); }); + component.update(); const latestRequest = server.requests[server.requests.length - 1]; diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx index 77e65dfd91c75..481f59093d7dc 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx @@ -9,15 +9,10 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import axios from 'axios'; + import { getExecuteDetails } from '../../__fixtures__'; import { WATCH_TYPES } from '../../common/constants'; -import { - setupEnvironment, - pageHelpers, - nextTick, - wrapBodyResponse, - unwrapBodyResponse, -} from './helpers'; +import { setupEnvironment, pageHelpers, wrapBodyResponse, unwrapBodyResponse } from './helpers'; import { WatchCreateThresholdTestBed } from './helpers/watch_create_threshold.helpers'; const WATCH_NAME = 'my_test_watch'; @@ -76,7 +71,9 @@ jest.mock('@elastic/eui', () => { // which does not produce a valid component wrapper EuiComboBox: (props: any) => ( { props.onChange([syntheticEvent['0']]); }} @@ -91,7 +88,12 @@ describe(' create route', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchCreateThresholdTestBed; + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); server.restore(); }); @@ -99,7 +101,6 @@ describe(' create route', () => { beforeEach(async () => { testBed = await setup(); const { component } = testBed; - await nextTick(); component.update(); }); @@ -159,46 +160,60 @@ describe(' create route', () => { test('it should enable the Create button and render additional content with valid fields', async () => { const { form, find, component, exists } = testBed; - form.setInputValue('nameInput', 'my_test_watch'); - find('mockComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox - form.setInputValue('watchTimeFieldSelect', '@timestamp'); + expect(find('saveWatchButton').props().disabled).toBe(true); await act(async () => { - await nextTick(); - component.update(); + form.setInputValue('nameInput', 'my_test_watch'); + find('indicesComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox + form.setInputValue('watchTimeFieldSelect', '@timestamp'); }); + component.update(); - expect(find('saveWatchButton').props().disabled).toEqual(false); - + expect(find('saveWatchButton').props().disabled).toBe(false); expect(find('watchConditionTitle').text()).toBe('Match the following condition'); expect(exists('watchVisualizationChart')).toBe(true); expect(exists('watchActionsPanel')).toBe(true); }); - // Looks like there is an issue with using 'mockComboBox'. - describe.skip('watch conditions', () => { - beforeEach(() => { - const { form, find } = testBed; + describe('watch conditions', () => { + beforeEach(async () => { + const { form, find, component } = testBed; // Name, index and time fields are required before the watch condition expression renders - form.setInputValue('nameInput', 'my_test_watch'); - act(() => { - find('mockComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox + await act(async () => { + form.setInputValue('nameInput', 'my_test_watch'); + find('indicesComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox + form.setInputValue('watchTimeFieldSelect', '@timestamp'); }); - form.setInputValue('watchTimeFieldSelect', '@timestamp'); + component.update(); }); - test('should require a threshold value', () => { - const { form, find } = testBed; + test('should require a threshold value', async () => { + const { form, find, component } = testBed; + // Display the threshold pannel act(() => { find('watchThresholdButton').simulate('click'); + }); + component.update(); + + await act(async () => { // Provide invalid value form.setInputValue('watchThresholdInput', ''); + }); + + // We need to wait for the debounced validation to be triggered and update the DOM + jest.advanceTimersByTime(500); + component.update(); + + expect(form.getErrorsMessages()).toContain('A value is required.'); + + await act(async () => { // Provide valid value form.setInputValue('watchThresholdInput', '0'); }); - expect(form.getErrorsMessages()).toContain('A value is required.'); + component.update(); + // No need to wait as the validation errors are cleared whenever the field changes expect(form.getErrorsMessages().length).toEqual(0); }); }); @@ -209,14 +224,12 @@ describe(' create route', () => { const { form, find, component } = testBed; // Set up valid fields needed for actions component to render - form.setInputValue('nameInput', WATCH_NAME); - find('mockComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox - form.setInputValue('watchTimeFieldSelect', WATCH_TIME_FIELD); - await act(async () => { - await nextTick(); - component.update(); + form.setInputValue('nameInput', WATCH_NAME); + find('indicesComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); + form.setInputValue('watchTimeFieldSelect', WATCH_TIME_FIELD); }); + component.update(); }); test('should simulate a logging action', async () => { @@ -240,7 +253,6 @@ describe(' create route', () => { await act(async () => { actions.clickSimulateButton(); - await nextTick(); }); // Verify request @@ -303,7 +315,6 @@ describe(' create route', () => { await act(async () => { actions.clickSimulateButton(); - await nextTick(); }); // Verify request @@ -366,7 +377,6 @@ describe(' create route', () => { await act(async () => { actions.clickSimulateButton(); - await nextTick(); }); // Verify request @@ -431,15 +441,14 @@ describe(' create route', () => { expect(exists('watchActionAccordion')).toBe(true); // Provide valid fields and verify - find('watchActionAccordion.mockComboBox').simulate('change', [ + find('watchActionAccordion.toEmailAddressInput').simulate('change', [ { label: EMAIL_RECIPIENT, value: EMAIL_RECIPIENT }, - ]); // Using mocked EuiComboBox + ]); form.setInputValue('emailSubjectInput', EMAIL_SUBJECT); form.setInputValue('emailBodyInput', EMAIL_BODY); await act(async () => { actions.clickSimulateButton(); - await nextTick(); }); // Verify request @@ -532,7 +541,6 @@ describe(' create route', () => { await act(async () => { actions.clickSimulateButton(); - await nextTick(); }); // Verify request @@ -621,7 +629,6 @@ describe(' create route', () => { await act(async () => { actions.clickSimulateButton(); - await nextTick(); }); // Verify request @@ -702,7 +709,6 @@ describe(' create route', () => { await act(async () => { actions.clickSimulateButton(); - await nextTick(); }); // Verify request @@ -753,20 +759,66 @@ describe(' create route', () => { }); }); + describe('watch visualize data payload', () => { + test('should send the correct payload', async () => { + const { form, find, component } = testBed; + + // Set up required fields + await act(async () => { + form.setInputValue('nameInput', WATCH_NAME); + find('indicesComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); + form.setInputValue('watchTimeFieldSelect', WATCH_TIME_FIELD); + }); + component.update(); + + const latestReqToGetVisualizeData = server.requests.find( + (req) => req.method === 'POST' && req.url === '/api/watcher/watch/visualize' + ); + if (!latestReqToGetVisualizeData) { + throw new Error(`No request found to fetch visualize data.`); + } + + const requestBody = unwrapBodyResponse(latestReqToGetVisualizeData.requestBody); + + expect(requestBody.watch).toEqual({ + id: requestBody.watch.id, // id is dynamic + name: 'my_test_watch', + type: 'threshold', + isNew: true, + isActive: true, + actions: [], + index: ['index1'], + timeField: '@timestamp', + triggerIntervalSize: 1, + triggerIntervalUnit: 'm', + aggType: 'count', + termSize: 5, + termOrder: 'desc', + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + hasTermsAgg: false, + threshold: 1000, + }); + + expect(requestBody.options.interval).toBeDefined(); + }); + }); + describe('form payload', () => { test('should send the correct payload', async () => { const { form, find, component, actions } = testBed; // Set up required fields - form.setInputValue('nameInput', WATCH_NAME); - find('mockComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox - form.setInputValue('watchTimeFieldSelect', WATCH_TIME_FIELD); + await act(async () => { + form.setInputValue('nameInput', WATCH_NAME); + find('indicesComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); + form.setInputValue('watchTimeFieldSelect', WATCH_TIME_FIELD); + }); + component.update(); await act(async () => { - await nextTick(); - component.update(); actions.clickSubmitButton(); - await nextTick(); }); // Verify request diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts index e8782edc829a4..1188cc8469a58 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_edit.test.ts @@ -12,7 +12,7 @@ import { getRandomString } from '@kbn/test/jest'; import { getWatch } from '../../__fixtures__'; import { defaultWatch } from '../../public/application/models/watch'; -import { setupEnvironment, pageHelpers, nextTick, wrapBodyResponse } from './helpers'; +import { setupEnvironment, pageHelpers, wrapBodyResponse } from './helpers'; import { WatchEditTestBed } from './helpers/watch_edit.helpers'; import { WATCH } from './helpers/jest_constants'; @@ -41,7 +41,12 @@ describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchEditTestBed; + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); server.restore(); }); @@ -50,11 +55,7 @@ describe('', () => { httpRequestsMockHelpers.setLoadWatchResponse(WATCH); testBed = await setup(); - - await act(async () => { - await nextTick(); - testBed.component.update(); - }); + testBed.component.update(); }); describe('on component mount', () => { @@ -87,7 +88,6 @@ describe('', () => { await act(async () => { actions.clickSubmitButton(); - await nextTick(); }); const latestRequest = server.requests[server.requests.length - 1]; @@ -141,12 +141,7 @@ describe('', () => { httpRequestsMockHelpers.setLoadWatchResponse({ watch }); testBed = await setup(); - - await act(async () => { - const { component } = testBed; - await nextTick(); - component.update(); - }); + testBed.component.update(); }); describe('on component mount', () => { @@ -172,7 +167,6 @@ describe('', () => { await act(async () => { actions.clickSubmitButton(); - await nextTick(); }); const latestRequest = server.requests[server.requests.length - 1]; diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_status.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_status.test.ts index c19ec62b94477..1b1b813617da6 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_status.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_status.test.ts @@ -9,7 +9,7 @@ import { act } from 'react-dom/test-utils'; import moment from 'moment'; import { getWatchHistory } from '../../__fixtures__'; import { ROUTES, WATCH_STATES, ACTION_STATES } from '../../common/constants'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; +import { setupEnvironment, pageHelpers } from './helpers'; import { WatchStatusTestBed } from './helpers/watch_status.helpers'; import { WATCH } from './helpers/jest_constants'; @@ -43,7 +43,12 @@ describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchStatusTestBed; + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); server.restore(); }); @@ -53,11 +58,7 @@ describe('', () => { httpRequestsMockHelpers.setLoadWatchHistoryResponse(watchHistoryItems); testBed = await setup(); - - await act(async () => { - await nextTick(); - testBed.component.update(); - }); + testBed.component.update(); }); test('should set the correct page title', () => { @@ -175,9 +176,8 @@ describe('', () => { await act(async () => { confirmButton!.click(); - await nextTick(); - component.update(); }); + component.update(); const latestRequest = server.requests[server.requests.length - 1]; diff --git a/x-pack/plugins/watcher/public/plugin.ts b/x-pack/plugins/watcher/public/plugin.ts index 6c6d6f1169658..093f34e70400f 100644 --- a/x-pack/plugins/watcher/public/plugin.ts +++ b/x-pack/plugins/watcher/public/plugin.ts @@ -8,13 +8,11 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin, CoreStart, Capabilities } from 'kibana/public'; import { first, map, skip } from 'rxjs/operators'; - import { Subject, combineLatest } from 'rxjs'; -import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; - -import { LicenseStatus } from '../common/types/license_status'; +import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; import { ILicense } from '../../licensing/public'; +import { LicenseStatus } from '../common/types/license_status'; import { PLUGIN } from '../common/constants'; import { Dependencies } from './types'; diff --git a/x-pack/plugins/watcher/server/models/watch/lib/get_interval_type/get_interval_type.test.ts b/x-pack/plugins/watcher/server/models/watch/lib/get_interval_type/get_interval_type.test.ts new file mode 100644 index 0000000000000..a90876d1baf2e --- /dev/null +++ b/x-pack/plugins/watcher/server/models/watch/lib/get_interval_type/get_interval_type.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getIntervalType } from './get_interval_type'; + +describe('get interval type', () => { + test('should detect fixed intervals', () => { + ['1ms', '1s', '1m', '1h', '1d', '21s', '7d'].forEach((interval) => { + const intervalDetected = getIntervalType(interval); + try { + expect(intervalDetected).toBe('fixed_interval'); + } catch (e) { + throw new Error( + `Expected [${interval}] to be a fixed interval but got [${intervalDetected}]` + ); + } + }); + }); + + test('should detect calendar intervals', () => { + ['1w', '1M', '1q', '1y'].forEach((interval) => { + const intervalDetected = getIntervalType(interval); + try { + expect(intervalDetected).toBe('calendar_interval'); + } catch (e) { + throw new Error( + `Expected [${interval}] to be a calendar interval but got [${intervalDetected}]` + ); + } + }); + }); +}); diff --git a/x-pack/plugins/watcher/server/models/watch/lib/get_interval_type/get_interval_type.ts b/x-pack/plugins/watcher/server/models/watch/lib/get_interval_type/get_interval_type.ts new file mode 100644 index 0000000000000..5e23523a133c4 --- /dev/null +++ b/x-pack/plugins/watcher/server/models/watch/lib/get_interval_type/get_interval_type.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Since 8.x we use the "fixed_interval" or "calendar_interval" parameter instead + * of the less precise "interval". This helper parse the interval and return its type. + * @param interval Interval value (e.g. "1d", "1w"...) + */ +export const getIntervalType = (interval: string): 'fixed_interval' | 'calendar_interval' => { + // We will consider all interval as fixed except if they are + // weekly (w), monthly (M), quarterly (q) or yearly (y) + const intervalMetric = interval.charAt(interval.length - 1); + if (['w', 'M', 'q', 'y'].includes(intervalMetric)) { + return 'calendar_interval'; + } + return 'fixed_interval'; +}; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/utils/index.ts b/x-pack/plugins/watcher/server/models/watch/lib/get_interval_type/index.ts similarity index 79% rename from x-pack/plugins/spaces/public/share_saved_objects_to_space/utils/index.ts rename to x-pack/plugins/watcher/server/models/watch/lib/get_interval_type/index.ts index a40bc87cd4dc3..0bb505e4ea722 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/utils/index.ts +++ b/x-pack/plugins/watcher/server/models/watch/lib/get_interval_type/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { createRedirectLegacyUrl } from './redirect_legacy_url'; +export { getIntervalType } from './get_interval_type'; diff --git a/x-pack/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js b/x-pack/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js index 10ba68c3193a0..60b2dd5a546be 100644 --- a/x-pack/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js +++ b/x-pack/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js @@ -6,8 +6,10 @@ */ import { cloneDeep } from 'lodash'; + import { buildInput } from '../../../../common/lib/serialization'; import { AGG_TYPES } from '../../../../common/constants'; +import { getIntervalType } from '../lib/get_interval_type'; /* input.search.request.body.query.bool.filter.range @@ -22,17 +24,6 @@ function buildRange({ rangeFrom, rangeTo, timeField }) { }; } -function buildDateAgg({ field, interval, timeZone }) { - return { - date_histogram: { - field, - interval, - time_zone: timeZone, - min_doc_count: 1, - }, - }; -} - function buildAggsCount(body, dateAgg) { return { dateAgg, @@ -93,7 +84,7 @@ function buildAggs(body, { aggType, termField }, dateAgg) { } } -export function buildVisualizeQuery(watch, visualizeOptions) { +export function buildVisualizeQuery(watch, visualizeOptions, kibanaVersion) { const { index, timeWindowSize, @@ -117,11 +108,22 @@ export function buildVisualizeQuery(watch, visualizeOptions) { termOrder, }); const body = watchInput.search.request.body; - const dateAgg = buildDateAgg({ - field: watch.timeField, - interval: visualizeOptions.interval, - timeZone: visualizeOptions.timezone, - }); + const dateAgg = { + date_histogram: { + field: watch.timeField, + time_zone: visualizeOptions.timezone, + min_doc_count: 1, + }, + }; + + if (kibanaVersion.major < 8) { + // In 7.x we use the deprecated "interval" in date_histogram agg + dateAgg.date_histogram.interval = visualizeOptions.interval; + } else { + // From 8.x we use the more precise "fixed_interval" or "calendar_interval" + const intervalType = getIntervalType(visualizeOptions.interval); + dateAgg.date_histogram[intervalType] = visualizeOptions.interval; + } // override the query range body.query.bool.filter.range = buildRange({ diff --git a/x-pack/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js b/x-pack/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js index 5cc8a5535c8c3..a20b83e83e3b7 100644 --- a/x-pack/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js +++ b/x-pack/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js @@ -48,8 +48,8 @@ export class ThresholdWatch extends BaseWatch { return serializeThresholdWatch(this); } - getVisualizeQuery(visualizeOptions) { - return buildVisualizeQuery(this, visualizeOptions); + getVisualizeQuery(visualizeOptions, kibanaVersion) { + return buildVisualizeQuery(this, visualizeOptions, kibanaVersion); } formatVisualizeData(results) { diff --git a/x-pack/plugins/watcher/server/plugin.ts b/x-pack/plugins/watcher/server/plugin.ts index aea8368c7bbed..52d77520183ab 100644 --- a/x-pack/plugins/watcher/server/plugin.ts +++ b/x-pack/plugins/watcher/server/plugin.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; - +import { SemVer } from 'semver'; import { CoreStart, CoreSetup, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; import { PLUGIN, INDEX_NAMES } from '../common/constants'; @@ -27,17 +27,19 @@ export class WatcherServerPlugin implements Plugin { private readonly license: License; private readonly logger: Logger; - constructor(ctx: PluginInitializerContext) { + constructor(private ctx: PluginInitializerContext) { this.logger = ctx.logger.get(); this.license = new License(); } - setup({ http, getStartServices }: CoreSetup, { licensing, features }: SetupDependencies) { + setup({ http }: CoreSetup, { features }: SetupDependencies) { this.license.setup({ pluginName: PLUGIN.getI18nName(i18n), logger: this.logger, }); + const kibanaVersion = new SemVer(this.ctx.env.packageInfo.version); + const router = http.createRouter(); const routeDependencies: RouteDependencies = { router, @@ -45,6 +47,7 @@ export class WatcherServerPlugin implements Plugin { lib: { handleEsError, }, + kibanaVersion, }; features.registerElasticsearchFeature({ diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts index 61836d0ebae47..60442bf43bd68 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts @@ -37,6 +37,7 @@ export function registerVisualizeRoute({ router, license, lib: { handleEsError }, + kibanaVersion, }: RouteDependencies) { router.post( { @@ -48,7 +49,7 @@ export function registerVisualizeRoute({ license.guardApiRoute(async (ctx, request, response) => { const watch = Watch.fromDownstreamJson(request.body.watch); const options = VisualizeOptions.fromDownstreamJson(request.body.options); - const body = watch.getVisualizeQuery(options); + const body = watch.getVisualizeQuery(options, kibanaVersion); try { const hits = await fetchVisualizeData(ctx.core.elasticsearch.client, watch.index, body); diff --git a/x-pack/plugins/watcher/server/types.ts b/x-pack/plugins/watcher/server/types.ts index c9d43528d9ffa..87cd5e40c2792 100644 --- a/x-pack/plugins/watcher/server/types.ts +++ b/x-pack/plugins/watcher/server/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SemVer } from 'semver'; import type { IRouter } from 'src/core/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; @@ -33,4 +34,5 @@ export interface RouteDependencies { lib: { handleEsError: typeof handleEsError; }; + kibanaVersion: SemVer; } diff --git a/x-pack/test/api_integration/apis/maps/get_tile.js b/x-pack/test/api_integration/apis/maps/get_tile.js index 03a16175931a5..b153cc1ff030c 100644 --- a/x-pack/test/api_integration/apis/maps/get_tile.js +++ b/x-pack/test/api_integration/apis/maps/get_tile.js @@ -13,7 +13,8 @@ import { MVT_SOURCE_LAYER_NAME } from '../../../../plugins/maps/common/constants export default function ({ getService }) { const supertest = getService('supertest'); - describe('getTile', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/114471 + describe.skip('getTile', () => { it('should return vector tile containing document', async () => { const resp = await supertest .get( diff --git a/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts b/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts index 3848330a95fb9..7124a9566c710 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts @@ -17,7 +17,7 @@ export default ({ getService }: FtrProviderContext) => { describe('DELETE trained_models', () => { before(async () => { await ml.testResources.setKibanaTimeZoneToUTC(); - await ml.api.createdTestTrainedModels('regression', 2); + await ml.api.createTestTrainedModels('regression', 2); }); after(async () => { diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts index cc347056f02a3..9600972e3e8be 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts @@ -19,7 +19,7 @@ export default ({ getService }: FtrProviderContext) => { before(async () => { await ml.testResources.setKibanaTimeZoneToUTC(); - testModelIds = await ml.api.createdTestTrainedModels('regression', 2, true); + testModelIds = await ml.api.createTestTrainedModels('regression', 2, true); }); after(async () => { diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts index 76f108836996f..48040959f0e4b 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts @@ -17,7 +17,7 @@ export default ({ getService }: FtrProviderContext) => { describe('GET trained_models/_stats', () => { before(async () => { await ml.testResources.setKibanaTimeZoneToUTC(); - await ml.api.createdTestTrainedModels('regression', 2); + await ml.api.createTestTrainedModels('regression', 2); }); after(async () => { diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts index 604dff6a98a9a..ec33ef316828c 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts @@ -19,7 +19,7 @@ export default ({ getService }: FtrProviderContext) => { before(async () => { await ml.testResources.setKibanaTimeZoneToUTC(); - testModelIds = await ml.api.createdTestTrainedModels('regression', 5, true); + testModelIds = await ml.api.createTestTrainedModels('regression', 5, true); await ml.api.createModelAlias('dfa_regression_model_n_0', 'dfa_regression_model_alias'); await ml.api.createIngestPipeline('dfa_regression_model_alias'); }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors.ts b/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors.ts index 8e80208b3d805..de65fe6deb985 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors.ts @@ -14,7 +14,8 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('es'); - describe('telemetry collectors heartbeat', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/111240 + describe.skip('telemetry collectors heartbeat', () => { before('generating data', async () => { await getService('esArchiver').load('x-pack/test/functional/es_archives/uptime/blank'); diff --git a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts index d3e5b08233487..621ed5dcfd8d7 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts @@ -33,7 +33,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('transaction_error_rate (without data)', async () => { const options = getOptions(); const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', + endpoint: 'GET /internal/apm/alerts/chart_preview/transaction_error_rate', ...options, }); @@ -46,7 +46,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { options.params.query.transactionType = undefined; const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', + endpoint: 'GET /internal/apm/alerts/chart_preview/transaction_error_count', ...options, }); @@ -58,7 +58,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const options = getOptions(); const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', + endpoint: 'GET /internal/apm/alerts/chart_preview/transaction_duration', ...options, }); @@ -71,7 +71,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('transaction_error_rate (with data)', async () => { const options = getOptions(); const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', + endpoint: 'GET /internal/apm/alerts/chart_preview/transaction_error_rate', ...options, }); @@ -88,7 +88,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { options.params.query.transactionType = undefined; const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', + endpoint: 'GET /internal/apm/alerts/chart_preview/transaction_error_count', ...options, }); @@ -104,7 +104,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const options = getOptions(); const response = await apmApiClient.readUser({ ...options, - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', + endpoint: 'GET /internal/apm/alerts/chart_preview/transaction_duration', }); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/feature_controls.ts b/x-pack/test/apm_api_integration/tests/feature_controls.ts index 18fcf4fef5fec..ef3ba5d61fb54 100644 --- a/x-pack/test/apm_api_integration/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -44,87 +44,89 @@ export default function featureControlsTests({ getService }: FtrProviderContext) { // this doubles as a smoke test for the _inspect query parameter req: { - url: `/api/apm/services/foo/errors?start=${start}&end=${end}&_inspect=true&environment=ENVIRONMENT_ALL&kuery=`, + url: `/internal/apm/services/foo/errors?start=${start}&end=${end}&_inspect=true&environment=ENVIRONMENT_ALL&kuery=`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/errors/bar?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=`, + url: `/internal/apm/services/foo/errors/bar?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/errors/distribution?start=${start}&end=${end}&groupId=bar&environment=ENVIRONMENT_ALL&kuery=`, + url: `/internal/apm/services/foo/errors/distribution?start=${start}&end=${end}&groupId=bar&environment=ENVIRONMENT_ALL&kuery=`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/errors/distribution?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=`, + url: `/internal/apm/services/foo/errors/distribution?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/metrics/charts?start=${start}&end=${end}&agentName=cool-agent&environment=ENVIRONMENT_ALL&kuery=`, + url: `/internal/apm/services/foo/metrics/charts?start=${start}&end=${end}&agentName=cool-agent&environment=ENVIRONMENT_ALL&kuery=`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=`, + url: `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/agent?start=${start}&end=${end}`, + url: `/internal/apm/services/foo/agent?start=${start}&end=${end}`, }, expectForbidden: expect403, expectResponse: expect200, }, { - req: { url: `/api/apm/services/foo/transaction_types?start=${start}&end=${end}` }, + req: { url: `/internal/apm/services/foo/transaction_types?start=${start}&end=${end}` }, expectForbidden: expect403, expectResponse: expect200, }, { - req: { url: `/api/apm/traces?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` }, + req: { + url: `/internal/apm/traces?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=`, + }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/traces/foo?start=${start}&end=${end}`, + url: `/internal/apm/traces/foo?start=${start}&end=${end}`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&kuery=`, + url: `/internal/apm/services/foo/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&kuery=`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&transactionName=baz&kuery=`, + url: `/internal/apm/services/foo/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&transactionName=baz&kuery=`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/transactions/traces/samples?start=${start}&end=${end}&transactionType=bar&transactionName=baz&environment=ENVIRONMENT_ALL&kuery=`, + url: `/internal/apm/services/foo/transactions/traces/samples?start=${start}&end=${end}&transactionType=bar&transactionName=baz&environment=ENVIRONMENT_ALL&kuery=`, }, expectForbidden: expect403, expectResponse: expect200, @@ -147,21 +149,21 @@ export default function featureControlsTests({ getService }: FtrProviderContext) }, { req: { - url: `/api/apm/settings/custom_links/transaction`, + url: `/internal/apm/settings/custom_links/transaction`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/metadata/details?start=${start}&end=${end}`, + url: `/internal/apm/services/foo/metadata/details?start=${start}&end=${end}`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/metadata/icons?start=${start}&end=${end}`, + url: `/internal/apm/services/foo/metadata/icons?start=${start}&end=${end}`, }, expectForbidden: expect403, expectResponse: expect200, diff --git a/x-pack/test/apm_api_integration/tests/historical_data/has_data.ts b/x-pack/test/apm_api_integration/tests/historical_data/has_data.ts index ec3f0e5e7f362..70048c3d39210 100644 --- a/x-pack/test/apm_api_integration/tests/historical_data/has_data.ts +++ b/x-pack/test/apm_api_integration/tests/historical_data/has_data.ts @@ -18,7 +18,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: [] }, () => { it('handles the empty state', async () => { - const response = await supertest.get(`/api/apm/has_data`); + const response = await supertest.get(`/internal/apm/has_data`); expect(response.status).to.be(200); expect(response.body.hasData).to.be(false); @@ -31,7 +31,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { { config: 'basic', archives: [archiveName] }, () => { it('returns hasData: true', async () => { - const response = await supertest.get(`/api/apm/has_data`); + const response = await supertest.get(`/internal/apm/has_data`); expect(response.status).to.be(200); expect(response.body.hasData).to.be(true); diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index efe159b36e3d3..49f1ae91c6282 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -150,7 +150,7 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte describe('traces/top_traces', function () { loadTestFile(require.resolve('./traces/top_traces')); }); - describe('/api/apm/traces/{traceId}', function () { + describe('/internal/apm/traces/{traceId}', function () { loadTestFile(require.resolve('./traces/trace_by_id')); }); diff --git a/x-pack/test/apm_api_integration/tests/inspect/inspect.ts b/x-pack/test/apm_api_integration/tests/inspect/inspect.ts index 95805f4ef4524..75b5a02fff800 100644 --- a/x-pack/test/apm_api_integration/tests/inspect/inspect.ts +++ b/x-pack/test/apm_api_integration/tests/inspect/inspect.ts @@ -21,7 +21,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { describe('when omitting `_inspect` query param', () => { it('returns response without `_inspect`', async () => { const { status, body } = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/environments', + endpoint: 'GET /internal/apm/environments', params: { query: { start: metadata.start, @@ -39,7 +39,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { describe('elasticsearch calls made with end-user auth are returned', () => { it('for environments', async () => { const { status, body } = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/environments', + endpoint: 'GET /internal/apm/environments', params: { query: { start: metadata.start, @@ -67,7 +67,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { describe('elasticsearch calls made with internal user are not return', () => { it('for custom links', async () => { const { status, body } = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/settings/custom_links', + endpoint: 'GET /internal/apm/settings/custom_links', params: { query: { 'service.name': 'opbeans-node', diff --git a/x-pack/test/apm_api_integration/tests/metadata/event_metadata.ts b/x-pack/test/apm_api_integration/tests/metadata/event_metadata.ts index d979f0bad1ec6..fb98cc9a6abd0 100644 --- a/x-pack/test/apm_api_integration/tests/metadata/event_metadata.ts +++ b/x-pack/test/apm_api_integration/tests/metadata/event_metadata.ts @@ -40,7 +40,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const id = await getLastDocId(ProcessorEvent.transaction); const { body } = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/event_metadata/{processorEvent}/{id}', + endpoint: 'GET /internal/apm/event_metadata/{processorEvent}/{id}', params: { path: { processorEvent: ProcessorEvent.transaction, @@ -70,7 +70,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const id = await getLastDocId(ProcessorEvent.error); const { body } = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/event_metadata/{processorEvent}/{id}', + endpoint: 'GET /internal/apm/event_metadata/{processorEvent}/{id}', params: { path: { processorEvent: ProcessorEvent.error, @@ -100,7 +100,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const id = await getLastDocId(ProcessorEvent.span); const { body } = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/event_metadata/{processorEvent}/{id}', + endpoint: 'GET /internal/apm/event_metadata/{processorEvent}/{id}', params: { path: { processorEvent: ProcessorEvent.span, diff --git a/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts index a3e02984a16de..8d3a18a44f02e 100644 --- a/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts +++ b/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts @@ -33,7 +33,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let chartsResponse: ChartResponse; before(async () => { chartsResponse = await supertest.get( - `/api/apm/services/opbeans-node/metrics/charts?start=${start}&end=${end}&agentName=${agentName}&kuery=&environment=ENVIRONMENT_ALL` + `/internal/apm/services/opbeans-node/metrics/charts?start=${start}&end=${end}&agentName=${agentName}&kuery=&environment=ENVIRONMENT_ALL` ); }); it('contains CPU usage and System memory usage chart data', async () => { @@ -121,7 +121,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let chartsResponse: ChartResponse; before(async () => { chartsResponse = await supertest.get( - `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&agentName=${agentName}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&agentName=${agentName}&environment=ENVIRONMENT_ALL&kuery=` ); }); @@ -410,7 +410,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const end = encodeURIComponent('2020-09-08T15:05:00.000Z'); const chartsResponse: ChartResponse = await supertest.get( - `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&agentName=${agentName}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&agentName=${agentName}&environment=ENVIRONMENT_ALL&kuery=` ); const systemMemoryUsageChart = chartsResponse.body.charts.find( diff --git a/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts index 1b0f8fdcf8736..90a01c472e13e 100644 --- a/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts @@ -19,7 +19,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('returns false when there is no data', async () => { const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/observability_overview/has_data', + endpoint: 'GET /internal/apm/observability_overview/has_data', }); expect(response.status).to.be(200); expect(response.body.hasData).to.eql(false); @@ -33,7 +33,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('returns false when there is only onboarding data', async () => { const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/observability_overview/has_data', + endpoint: 'GET /internal/apm/observability_overview/has_data', }); expect(response.status).to.be(200); expect(response.body.hasData).to.eql(false); @@ -47,7 +47,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('returns true when there is at least one document on transaction, error or metrics indices', async () => { const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/observability_overview/has_data', + endpoint: 'GET /internal/apm/observability_overview/has_data', }); expect(response.status).to.be(200); expect(response.body.hasData).to.eql(true); diff --git a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts index 76a157d72cc6f..b463db81e6c99 100644 --- a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts @@ -28,7 +28,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('when data is not loaded', () => { it('handles the empty state', async () => { const response = await supertest.get( - `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` + `/internal/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` ); expect(response.status).to.be(200); @@ -45,7 +45,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('returns the service count and transaction coordinates', async () => { const response = await supertest.get( - `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` + `/internal/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` ); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap b/x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap index c66609b8a8a91..9e32f311e8d11 100644 --- a/x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap +++ b/x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map returns the correct data 3`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /internal/apm/service-map returns the correct data 3`] = ` Array [ Object { "data": Object { @@ -1376,7 +1376,7 @@ Array [ ] `; -exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map with ML data with the default apm user returns the correct anomaly stats 3`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /internal/apm/service-map with ML data with the default apm user returns the correct anomaly stats 3`] = ` Object { "elements": Array [ Object { diff --git a/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts index 816e4e26ef869..37fe340d75194 100644 --- a/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts @@ -28,7 +28,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) registry.when('Service map with a basic license', { config: 'basic', archives: [] }, () => { it('is only be available to users with Platinum license (or higher)', async () => { const response = await supertest.get( - `/api/apm/service-map?start=${start}&end=${end}&environment=ENVIRONMENT_ALL` + `/internal/apm/service-map?start=${start}&end=${end}&environment=ENVIRONMENT_ALL` ); expect(response.status).to.be(403); @@ -40,10 +40,10 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) }); registry.when('Service map without data', { config: 'trial', archives: [] }, () => { - describe('/api/apm/service-map', () => { + describe('/internal/apm/service-map', () => { it('returns an empty list', async () => { const response = await supertest.get( - `/api/apm/service-map?start=${start}&end=${end}&environment=ENVIRONMENT_ALL` + `/internal/apm/service-map?start=${start}&end=${end}&environment=ENVIRONMENT_ALL` ); expect(response.status).to.be(200); @@ -51,14 +51,14 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) }); }); - describe('/api/apm/service-map/service/{serviceName}', () => { + describe('/internal/apm/service-map/service/{serviceName}', () => { it('returns an object with nulls', async () => { const q = querystring.stringify({ start: metadata.start, end: metadata.end, environment: 'ENVIRONMENT_ALL', }); - const response = await supertest.get(`/api/apm/service-map/service/opbeans-node?${q}`); + const response = await supertest.get(`/internal/apm/service-map/service/opbeans-node?${q}`); expect(response.status).to.be(200); @@ -76,14 +76,14 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) }); }); - describe('/api/apm/service-map/backend/{backendName}', () => { + describe('/internal/apm/service-map/backend/{backendName}', () => { it('returns an object with nulls', async () => { const q = querystring.stringify({ start: metadata.start, end: metadata.end, environment: 'ENVIRONMENT_ALL', }); - const response = await supertest.get(`/api/apm/service-map/backend/postgres?${q}`); + const response = await supertest.get(`/internal/apm/service-map/backend/postgres?${q}`); expect(response.status).to.be(200); @@ -101,12 +101,12 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) }); registry.when('Service Map with data', { config: 'trial', archives: ['apm_8.0.0'] }, () => { - describe('/api/apm/service-map', () => { + describe('/internal/apm/service-map', () => { let response: PromiseReturnType; before(async () => { response = await supertest.get( - `/api/apm/service-map?start=${start}&end=${end}&environment=ENVIRONMENT_ALL` + `/internal/apm/service-map?start=${start}&end=${end}&environment=ENVIRONMENT_ALL` ); }); @@ -159,7 +159,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) describe('with the default apm user', () => { before(async () => { response = await supertest.get( - `/api/apm/service-map?start=${start}&end=${end}&environment=ENVIRONMENT_ALL` + `/internal/apm/service-map?start=${start}&end=${end}&environment=ENVIRONMENT_ALL` ); }); @@ -246,7 +246,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) describe('with a user that does not have access to ML', () => { before(async () => { response = await supertestAsApmReadUserWithoutMlAccess.get( - `/api/apm/service-map?start=${start}&end=${end}&environment=ENVIRONMENT_ALL` + `/internal/apm/service-map?start=${start}&end=${end}&environment=ENVIRONMENT_ALL` ); }); @@ -267,7 +267,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) it('returns service map elements', async () => { response = await supertest.get( url.format({ - pathname: '/api/apm/service-map', + pathname: '/internal/apm/service-map', query: { environment: 'ENVIRONMENT_ALL', start: metadata.start, @@ -284,14 +284,14 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) }); }); - describe('/api/apm/service-map/service/{serviceName}', () => { + describe('/internal/apm/service-map/service/{serviceName}', () => { it('returns an object with data', async () => { const q = querystring.stringify({ start: metadata.start, end: metadata.end, environment: 'ENVIRONMENT_ALL', }); - const response = await supertest.get(`/api/apm/service-map/service/opbeans-node?${q}`); + const response = await supertest.get(`/internal/apm/service-map/service/opbeans-node?${q}`); expect(response.status).to.be(200); @@ -309,14 +309,14 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) }); }); - describe('/api/apm/service-map/backend/{backendName}', () => { + describe('/internal/apm/service-map/backend/{backendName}', () => { it('returns an object with data', async () => { const q = querystring.stringify({ start: metadata.start, end: metadata.end, environment: 'ENVIRONMENT_ALL', }); - const response = await supertest.get(`/api/apm/service-map/backend/postgresql?${q}`); + const response = await supertest.get(`/internal/apm/service-map/backend/postgresql?${q}`); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts index 4bd9785b31427..1e8acccbb192a 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts @@ -37,7 +37,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('handles the empty state', async () => { const response = await apmApiClient.readUser({ - endpoint: `GET /api/apm/services/{serviceName}/dependencies`, + endpoint: `GET /internal/apm/services/{serviceName}/dependencies`, params: { path: { serviceName: 'opbeans-java' }, query: { @@ -61,7 +61,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { let response: { status: number; - body: APIReturnType<'GET /api/apm/services/{serviceName}/dependencies'>; + body: APIReturnType<'GET /internal/apm/services/{serviceName}/dependencies'>; }; const indices = { @@ -212,7 +212,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); response = await apmApiClient.readUser({ - endpoint: `GET /api/apm/services/{serviceName}/dependencies`, + endpoint: `GET /internal/apm/services/{serviceName}/dependencies`, params: { path: { serviceName: 'opbeans-java' }, query: { @@ -309,12 +309,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { let response: { status: number; - body: APIReturnType<'GET /api/apm/services/{serviceName}/dependencies'>; + body: APIReturnType<'GET /internal/apm/services/{serviceName}/dependencies'>; }; before(async () => { response = await apmApiClient.readUser({ - endpoint: `GET /api/apm/services/{serviceName}/dependencies`, + endpoint: `GET /internal/apm/services/{serviceName}/dependencies`, params: { path: { serviceName: 'opbeans-python' }, query: { diff --git a/x-pack/test/apm_api_integration/tests/service_overview/get_service_node_ids.ts b/x-pack/test/apm_api_integration/tests/service_overview/get_service_node_ids.ts index 1472c1f8c1a09..019c41f9292ba 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/get_service_node_ids.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/get_service_node_ids.ts @@ -22,7 +22,7 @@ export async function getServiceNodeIds({ count?: number; }) { const { body } = await apmApiSupertest({ - endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, + endpoint: `GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`, params: { path: { serviceName }, query: { diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instance_details.ts b/x-pack/test/apm_api_integration/tests/service_overview/instance_details.ts index d2b6136fafebf..f3de8823199a8 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instance_details.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instance_details.ts @@ -14,7 +14,7 @@ import { getServiceNodeIds } from './get_service_node_ids'; import { createApmApiClient } from '../../common/apm_api_supertest'; type ServiceOverviewInstanceDetails = - APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); @@ -31,7 +31,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles empty state', async () => { const response = await supertest.get( url.format({ - pathname: '/api/apm/services/opbeans-java/service_overview_instances/details/foo', + pathname: + '/internal/apm/services/opbeans-java/service_overview_instances/details/foo', query: { start, end, @@ -62,7 +63,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { serviceNodeIds = await getServiceNodeIds({ apmApiSupertest, start, end }); response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/service_overview_instances/details/${serviceNodeIds[0]}`, + pathname: `/internal/apm/services/opbeans-java/service_overview_instances/details/${serviceNodeIds[0]}`, query: { start, end, @@ -89,7 +90,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles empty state when instance id not found', async () => { const response = await supertest.get( url.format({ - pathname: '/api/apm/services/opbeans-java/service_overview_instances/details/foo', + pathname: '/internal/apm/services/opbeans-java/service_overview_instances/details/foo', query: { start, end, diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.ts index 1ad272bafaa80..9d9fb6a221cf6 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.ts @@ -26,7 +26,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { interface Response { status: number; - body: APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; + body: APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; } registry.when( @@ -37,7 +37,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response: Response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/service_overview_instances/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/service_overview_instances/detailed_statistics`, query: { latencyAggregationType: 'avg', start, @@ -75,7 +75,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { beforeEach(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/service_overview_instances/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/service_overview_instances/detailed_statistics`, query: { latencyAggregationType: 'avg', start, @@ -129,7 +129,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { beforeEach(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/service_overview_instances/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/service_overview_instances/detailed_statistics`, query: { latencyAggregationType: 'avg', numBuckets: 20, diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts index 355778757af3c..2d165f4ceb902 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts @@ -29,7 +29,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('when data is not loaded', () => { it('handles the empty state', async () => { const response = await apmApiClient.readUser({ - endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, + endpoint: `GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`, params: { path: { serviceName: 'opbeans-java' }, query: { @@ -58,12 +58,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { describe('fetching java data', () => { let response: { - body: APIReturnType<`GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`>; + body: APIReturnType<`GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`>; }; beforeEach(async () => { response = await apmApiClient.readUser({ - endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, + endpoint: `GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`, params: { path: { serviceName: 'opbeans-java' }, query: { @@ -129,12 +129,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('fetching non-java data', () => { let response: { - body: APIReturnType<`GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`>; + body: APIReturnType<`GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`>; }; beforeEach(async () => { response = await apmApiClient.readUser({ - endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, + endpoint: `GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`, params: { path: { serviceName: 'opbeans-ruby' }, query: { @@ -197,12 +197,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { describe('fetching java data', () => { let response: { - body: APIReturnType<`GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`>; + body: APIReturnType<`GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`>; }; beforeEach(async () => { response = await apmApiClient.readUser({ - endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, + endpoint: `GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`, params: { path: { serviceName: 'opbeans-java' }, query: { diff --git a/x-pack/test/apm_api_integration/tests/services/agent.ts b/x-pack/test/apm_api_integration/tests/services/agent.ts index 3e44dbe685cd8..fcd21f5a1a072 100644 --- a/x-pack/test/apm_api_integration/tests/services/agent.ts +++ b/x-pack/test/apm_api_integration/tests/services/agent.ts @@ -21,7 +21,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Agent name when data is not loaded', { config: 'basic', archives: [] }, () => { it('handles the empty state', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/agent?start=${start}&end=${end}` + `/internal/apm/services/opbeans-node/agent?start=${start}&end=${end}` ); expect(response.status).to.be(200); @@ -35,7 +35,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('returns the agent name', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/agent?start=${start}&end=${end}` + `/internal/apm/services/opbeans-node/agent?start=${start}&end=${end}` ); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts index 7c7a5b5e9c51f..3587a3e96c0d1 100644 --- a/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts @@ -16,7 +16,7 @@ import { createApmApiClient } from '../../common/apm_api_supertest'; import { getErrorGroupIds } from './get_error_group_ids'; type ErrorGroupsDetailedStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/detailed_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); @@ -35,7 +35,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/error_groups/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, query: { start, end, @@ -63,7 +63,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/error_groups/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, query: { start, end, @@ -98,7 +98,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns an empty state when requested groupIds are not available in the given time range', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/error_groups/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, query: { start, end, @@ -133,7 +133,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/error_groups/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, query: { numBuckets: 20, transactionType: 'request', @@ -179,7 +179,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns an empty state when requested groupIds are not available in the given time range', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/error_groups/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, query: { numBuckets: 20, transactionType: 'request', diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts index 98c906fe61192..b6fb0696f3f74 100644 --- a/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts @@ -13,7 +13,7 @@ import { registry } from '../../common/registry'; import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; type ErrorGroupsMainStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); @@ -29,7 +29,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles empty state', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/error_groups/main_statistics`, + pathname: `/internal/apm/services/opbeans-java/error_groups/main_statistics`, query: { start, end, @@ -56,7 +56,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the correct data', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/error_groups/main_statistics`, + pathname: `/internal/apm/services/opbeans-java/error_groups/main_statistics`, query: { start, end, diff --git a/x-pack/test/apm_api_integration/tests/services/get_error_group_ids.ts b/x-pack/test/apm_api_integration/tests/services/get_error_group_ids.ts index b2253750ae7f9..9fa7232240db1 100644 --- a/x-pack/test/apm_api_integration/tests/services/get_error_group_ids.ts +++ b/x-pack/test/apm_api_integration/tests/services/get_error_group_ids.ts @@ -21,7 +21,7 @@ export async function getErrorGroupIds({ count?: number; }) { const { body } = await apmApiSupertest({ - endpoint: `GET /api/apm/services/{serviceName}/error_groups/main_statistics`, + endpoint: `GET /internal/apm/services/{serviceName}/error_groups/main_statistics`, params: { path: { serviceName }, query: { diff --git a/x-pack/test/apm_api_integration/tests/services/service_details.ts b/x-pack/test/apm_api_integration/tests/services/service_details.ts index 263aaa504946b..28a3ee3ef82fd 100644 --- a/x-pack/test/apm_api_integration/tests/services/service_details.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_details.ts @@ -24,7 +24,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/metadata/details`, + pathname: `/internal/apm/services/opbeans-java/metadata/details`, query: { start, end }, }) ); @@ -42,7 +42,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns java service details', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/metadata/details`, + pathname: `/internal/apm/services/opbeans-java/metadata/details`, query: { start, end }, }) ); @@ -88,7 +88,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns python service details', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-python/metadata/details`, + pathname: `/internal/apm/services/opbeans-python/metadata/details`, query: { start, end }, }) ); diff --git a/x-pack/test/apm_api_integration/tests/services/service_icons.ts b/x-pack/test/apm_api_integration/tests/services/service_icons.ts index 619603efc4128..e948c6981d6c6 100644 --- a/x-pack/test/apm_api_integration/tests/services/service_icons.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_icons.ts @@ -21,7 +21,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/metadata/icons`, + pathname: `/internal/apm/services/opbeans-java/metadata/icons`, query: { start, end }, }) ); @@ -38,7 +38,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns java service icons', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/metadata/icons`, + pathname: `/internal/apm/services/opbeans-java/metadata/icons`, query: { start, end }, }) ); @@ -57,7 +57,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns python service icons', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-python/metadata/icons`, + pathname: `/internal/apm/services/opbeans-python/metadata/icons`, query: { start, end }, }) ); diff --git a/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.ts index c5c1032a794f9..d605c5f313825 100644 --- a/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/services/services_detailed_statistics.ts @@ -13,7 +13,8 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number'; -type ServicesDetailedStatisticsReturn = APIReturnType<'GET /api/apm/services/detailed_statistics'>; +type ServicesDetailedStatisticsReturn = + APIReturnType<'GET /internal/apm/services/detailed_statistics'>; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); @@ -30,7 +31,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/detailed_statistics`, + pathname: `/internal/apm/services/detailed_statistics`, query: { start, end, @@ -56,7 +57,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/detailed_statistics`, + pathname: `/internal/apm/services/detailed_statistics`, query: { start, end, @@ -107,7 +108,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns empty when empty service names is passed', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/detailed_statistics`, + pathname: `/internal/apm/services/detailed_statistics`, query: { start, end, @@ -124,7 +125,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('filters by environment', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/detailed_statistics`, + pathname: `/internal/apm/services/detailed_statistics`, query: { start, end, @@ -141,7 +142,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('filters by kuery', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/detailed_statistics`, + pathname: `/internal/apm/services/detailed_statistics`, query: { start, end, @@ -165,7 +166,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/detailed_statistics`, + pathname: `/internal/apm/services/detailed_statistics`, query: { start: moment(end).subtract(15, 'minutes').toISOString(), end, diff --git a/x-pack/test/apm_api_integration/tests/services/throughput.ts b/x-pack/test/apm_api_integration/tests/services/throughput.ts index 53c7efe28a6b5..d960cccc2877e 100644 --- a/x-pack/test/apm_api_integration/tests/services/throughput.ts +++ b/x-pack/test/apm_api_integration/tests/services/throughput.ts @@ -17,7 +17,7 @@ import archives_metadata from '../../common/fixtures/es_archiver/archives_metada import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; -type ThroughputReturn = APIReturnType<'GET /api/apm/services/{serviceName}/throughput'>; +type ThroughputReturn = APIReturnType<'GET /internal/apm/services/{serviceName}/throughput'>; export default function ApiTest({ getService }: FtrProviderContext) { const apmApiClient = getService('apmApiClient'); @@ -92,7 +92,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { kuery?: string; }) { const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/services/{serviceName}/throughput', + endpoint: 'GET /internal/apm/services/{serviceName}/throughput', params: { path: { serviceName: 'synth-go', @@ -166,7 +166,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { it('handles the empty state', async () => { const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/services/{serviceName}/throughput', + endpoint: 'GET /internal/apm/services/{serviceName}/throughput', params: { path: { serviceName: 'opbeans-java', @@ -195,7 +195,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('when querying without kql filter', () => { before(async () => { const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/services/{serviceName}/throughput', + endpoint: 'GET /internal/apm/services/{serviceName}/throughput', params: { path: { serviceName: 'opbeans-java', @@ -249,7 +249,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('with kql filter to force transaction-based UI', () => { before(async () => { const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/services/{serviceName}/throughput', + endpoint: 'GET /internal/apm/services/{serviceName}/throughput', params: { path: { serviceName: 'opbeans-java', @@ -285,7 +285,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { before(async () => { const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/services/{serviceName}/throughput', + endpoint: 'GET /internal/apm/services/{serviceName}/throughput', params: { path: { serviceName: 'opbeans-java', diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.ts b/x-pack/test/apm_api_integration/tests/services/top_services.ts index 8e79d6cda58e1..18700fb041252 100644 --- a/x-pack/test/apm_api_integration/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/tests/services/top_services.ts @@ -33,7 +33,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('handles the empty state', async () => { const response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` ); expect(response.status).to.be(200); @@ -49,14 +49,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { let response: { status: number; - body: APIReturnType<'GET /api/apm/services'>; + body: APIReturnType<'GET /internal/apm/services'>; }; let sortedItems: typeof response.body.items; before(async () => { response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` ); sortedItems = sortBy(response.body.items, 'serviceName'); }); @@ -192,15 +192,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('includes services that only report metric data', async () => { interface Response { status: number; - body: APIReturnType<'GET /api/apm/services'>; + body: APIReturnType<'GET /internal/apm/services'>; } const [unfilteredResponse, filteredResponse] = await Promise.all([ supertest.get( - `/api/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` ) as Promise, supertest.get( - `/api/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=${encodeURIComponent( + `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=${encodeURIComponent( 'not (processor.event:transaction)' )}` ) as Promise, @@ -231,12 +231,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('and fetching a list of services', () => { let response: { status: number; - body: APIReturnType<'GET /api/apm/services'>; + body: APIReturnType<'GET /internal/apm/services'>; }; before(async () => { response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` ); }); @@ -282,7 +282,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let response: PromiseReturnType; before(async () => { response = await supertestAsApmReadUserWithoutMlAccess.get( - `/api/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` ); }); @@ -307,7 +307,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let response: PromiseReturnType; before(async () => { response = await supertest.get( - `/api/apm/services?environment=ENVIRONMENT_ALL&start=${start}&end=${end}&kuery=${encodeURIComponent( + `/internal/apm/services?environment=ENVIRONMENT_ALL&start=${start}&end=${end}&kuery=${encodeURIComponent( 'service.name:opbeans-java' )}` ); diff --git a/x-pack/test/apm_api_integration/tests/services/transaction_types.ts b/x-pack/test/apm_api_integration/tests/services/transaction_types.ts index 6f574b5c8e997..4b752971e82f8 100644 --- a/x-pack/test/apm_api_integration/tests/services/transaction_types.ts +++ b/x-pack/test/apm_api_integration/tests/services/transaction_types.ts @@ -26,7 +26,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('handles empty state', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction_types?start=${start}&end=${end}` + `/internal/apm/services/opbeans-node/transaction_types?start=${start}&end=${end}` ); expect(response.status).to.be(200); @@ -42,7 +42,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('handles empty state', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction_types?start=${start}&end=${end}` + `/internal/apm/services/opbeans-node/transaction_types?start=${start}&end=${end}` ); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts index 40708adb754a8..7e05bd3faabfa 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts @@ -17,12 +17,12 @@ export default function apiTest({ getService }: FtrProviderContext) { type SupertestAsUser = typeof noAccessUser | typeof readUser | typeof writeUser; function getJobs(user: SupertestAsUser) { - return user.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + return user.get(`/internal/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); } function createJobs(user: SupertestAsUser, environments: string[]) { return user - .post(`/api/apm/settings/anomaly-detection/jobs`) + .post(`/internal/apm/settings/anomaly-detection/jobs`) .send({ environments }) .set('kbn-xsrf', 'foo'); } diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts index 135038755dc6e..0b73b67c8e4c2 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts @@ -13,12 +13,12 @@ export default function apiTest({ getService }: FtrProviderContext) { const noAccessUser = getService('legacySupertestAsNoAccessUser'); function getJobs() { - return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + return noAccessUser.get(`/internal/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); } function createJobs(environments: string[]) { return noAccessUser - .post(`/api/apm/settings/anomaly-detection/jobs`) + .post(`/internal/apm/settings/anomaly-detection/jobs`) .send({ environments }) .set('kbn-xsrf', 'foo'); } diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts index 3beebb434b317..982a47001f826 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts @@ -13,12 +13,12 @@ export default function apiTest({ getService }: FtrProviderContext) { const apmReadUser = getService('legacySupertestAsApmReadUser'); function getJobs() { - return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + return apmReadUser.get(`/internal/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); } function createJobs(environments: string[]) { return apmReadUser - .post(`/api/apm/settings/anomaly-detection/jobs`) + .post(`/internal/apm/settings/anomaly-detection/jobs`) .send({ environments }) .set('kbn-xsrf', 'foo'); } diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts index 7c13533a14291..3d671c8fb825e 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts @@ -15,12 +15,14 @@ export default function apiTest({ getService }: FtrProviderContext) { const legacyWriteUserClient = getService('legacySupertestAsApmWriteUser'); function getJobs() { - return apmApiClient.writeUser({ endpoint: `GET /api/apm/settings/anomaly-detection/jobs` }); + return apmApiClient.writeUser({ + endpoint: `GET /internal/apm/settings/anomaly-detection/jobs`, + }); } function createJobs(environments: string[]) { return apmApiClient.writeUser({ - endpoint: `POST /api/apm/settings/anomaly-detection/jobs`, + endpoint: `POST /internal/apm/settings/anomaly-detection/jobs`, params: { body: { environments }, }, diff --git a/x-pack/test/apm_api_integration/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts index 03b2ad4aa3212..86f60be3fdc4c 100644 --- a/x-pack/test/apm_api_integration/tests/settings/custom_link.ts +++ b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts @@ -126,7 +126,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { it('fetches a transaction sample', async () => { const response = await apmApiClient.readUser({ - endpoint: 'GET /api/apm/settings/custom_links/transaction', + endpoint: 'GET /internal/apm/settings/custom_links/transaction', params: { query: { 'service.name': 'opbeans-java', @@ -141,7 +141,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { function searchCustomLinks(filters?: any) { return apmApiClient.readUser({ - endpoint: 'GET /api/apm/settings/custom_links', + endpoint: 'GET /internal/apm/settings/custom_links', params: { query: filters, }, @@ -152,7 +152,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { log.debug('creating configuration', customLink); return apmApiClient.writeUser({ - endpoint: 'POST /api/apm/settings/custom_links', + endpoint: 'POST /internal/apm/settings/custom_links', params: { body: customLink, }, @@ -163,7 +163,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { log.debug('updating configuration', id, customLink); return apmApiClient.writeUser({ - endpoint: 'PUT /api/apm/settings/custom_links/{id}', + endpoint: 'PUT /internal/apm/settings/custom_links/{id}', params: { path: { id }, body: customLink, @@ -175,7 +175,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) { log.debug('deleting configuration', id); return apmApiClient.writeUser({ - endpoint: 'DELETE /api/apm/settings/custom_links/{id}', + endpoint: 'DELETE /internal/apm/settings/custom_links/{id}', params: { path: { id } }, }); } diff --git a/x-pack/test/apm_api_integration/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts index 705c3d9ff4a15..4968732f82203 100644 --- a/x-pack/test/apm_api_integration/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts @@ -24,7 +24,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Top traces when data is not loaded', { config: 'basic', archives: [] }, () => { it('handles empty state', async () => { const response = await supertest.get( - `/api/apm/traces?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/traces?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` ); expect(response.status).to.be(200); @@ -39,7 +39,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let response: any; before(async () => { response = await supertest.get( - `/api/apm/traces?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/traces?start=${start}&end=${end}&environment=ENVIRONMENT_ALL&kuery=` ); }); diff --git a/x-pack/test/apm_api_integration/tests/traces/trace_by_id.tsx b/x-pack/test/apm_api_integration/tests/traces/trace_by_id.tsx index 5b4ab5f45da49..d99a2b95a792e 100644 --- a/x-pack/test/apm_api_integration/tests/traces/trace_by_id.tsx +++ b/x-pack/test/apm_api_integration/tests/traces/trace_by_id.tsx @@ -22,7 +22,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Trace does not exist', { config: 'basic', archives: [] }, () => { it('handles empty state', async () => { const response = await apmApiSupertest({ - endpoint: `GET /api/apm/traces/{traceId}`, + endpoint: `GET /internal/apm/traces/{traceId}`, params: { path: { traceId: 'foo' }, query: { start, end }, @@ -35,10 +35,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); registry.when('Trace exists', { config: 'basic', archives: [archiveName] }, () => { - let response: SupertestReturnType<`GET /api/apm/traces/{traceId}`>; + let response: SupertestReturnType<`GET /internal/apm/traces/{traceId}`>; before(async () => { response = await apmApiSupertest({ - endpoint: `GET /api/apm/traces/{traceId}`, + endpoint: `GET /internal/apm/traces/{traceId}`, params: { path: { traceId: '64d0014f7530df24e549dd17cc0a8895' }, query: { start, end }, diff --git a/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts b/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts index de23b8fea5363..bc2e2f534a0bb 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts @@ -24,7 +24,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Breakdown when data is not loaded', { config: 'basic', archives: [] }, () => { it('handles the empty state', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&transactionType=${transactionType}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&transactionType=${transactionType}&environment=ENVIRONMENT_ALL&kuery=` ); expect(response.status).to.be(200); expect(response.body).to.eql({ timeseries: [] }); @@ -37,7 +37,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { it('returns the transaction breakdown for a service', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&transactionType=${transactionType}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&transactionType=${transactionType}&environment=ENVIRONMENT_ALL&kuery=` ); expect(response.status).to.be(200); @@ -45,7 +45,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the transaction breakdown for a transaction group', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&transactionType=${transactionType}&transactionName=${transactionName}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&transactionType=${transactionType}&transactionName=${transactionName}&environment=ENVIRONMENT_ALL&kuery=` ); expect(response.status).to.be(200); @@ -104,7 +104,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the transaction breakdown sorted by name', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&transactionType=${transactionType}&environment=ENVIRONMENT_ALL&kuery=` + `/internal/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&transactionType=${transactionType}&environment=ENVIRONMENT_ALL&kuery=` ); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts index d06eebd2cb982..2ddaa4677394c 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts @@ -15,7 +15,7 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; type ErrorRate = - APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/error_rate'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate'>; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); @@ -30,7 +30,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( format({ - pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate', + pathname: '/internal/apm/services/opbeans-java/transactions/charts/error_rate', query: { start, end, transactionType, environment: 'ENVIRONMENT_ALL', kuery: '' }, }) ); @@ -46,7 +46,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state with comparison data', async () => { const response = await supertest.get( format({ - pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate', + pathname: '/internal/apm/services/opbeans-java/transactions/charts/error_rate', query: { transactionType, start: moment(end).subtract(15, 'minutes').toISOString(), @@ -78,7 +78,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { const response = await supertest.get( format({ - pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate', + pathname: '/internal/apm/services/opbeans-java/transactions/charts/error_rate', query: { start, end, transactionType, environment: 'ENVIRONMENT_ALL', kuery: '' }, }) ); @@ -138,7 +138,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { const response = await supertest.get( format({ - pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate', + pathname: '/internal/apm/services/opbeans-java/transactions/charts/error_rate', query: { transactionType, start: moment(end).subtract(15, 'minutes').toISOString(), diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.ts index df6daa8973801..beaff7647868a 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/latency.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/latency.ts @@ -15,7 +15,7 @@ import archives_metadata from '../../common/fixtures/es_archiver/archives_metada import { registry } from '../../common/registry'; type LatencyChartReturnType = - APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/latency'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/charts/latency'>; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); @@ -31,7 +31,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns 400 when latencyAggregationType is not informed', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-node/transactions/charts/latency`, query: { start, end, @@ -47,7 +47,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns 400 when transactionType is not informed', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-node/transactions/charts/latency`, query: { start, end, @@ -63,7 +63,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-node/transactions/charts/latency`, query: { start, end, @@ -96,7 +96,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-node/transactions/charts/latency`, query: { start, end, @@ -121,7 +121,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-node/transactions/charts/latency`, query: { start, end, @@ -146,7 +146,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-node/transactions/charts/latency`, query: { start, end, @@ -176,7 +176,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-node/transactions/charts/latency`, query: { latencyAggregationType: 'avg', transactionType: 'request', @@ -217,7 +217,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-node/transactions/charts/latency`, query: { start, end, @@ -257,7 +257,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-java/transactions/charts/latency`, query: { start, end, @@ -279,7 +279,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-python/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-python/transactions/charts/latency`, query: { start, end, @@ -319,7 +319,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/charts/latency`, + pathname: `/internal/apm/services/opbeans-java/transactions/charts/latency`, query: { start, end, diff --git a/x-pack/test/apm_api_integration/tests/transactions/trace_samples.ts b/x-pack/test/apm_api_integration/tests/transactions/trace_samples.ts index fca9222e69bd0..19e75ff08fec2 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/trace_samples.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/trace_samples.ts @@ -17,7 +17,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; - const url = `/api/apm/services/opbeans-java/transactions/traces/samples?${qs.stringify({ + const url = `/internal/apm/services/opbeans-java/transactions/traces/samples?${qs.stringify({ environment: 'ENVIRONMENT_ALL', kuery: '', start: metadata.start, diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts index 84d7a2986ecb3..add954f490db1 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts @@ -16,7 +16,7 @@ import { registry } from '../../common/registry'; import { removeEmptyCoordinates, roundNumber } from '../../utils'; type TransactionsGroupsDetailedStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/detailed_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics'>; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); @@ -32,7 +32,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/transactions/groups/detailed_statistics`, query: { start, end, @@ -59,7 +59,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the correct data', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/transactions/groups/detailed_statistics`, query: { start, end, @@ -113,7 +113,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the correct data for latency aggregation 99th percentile', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/transactions/groups/detailed_statistics`, query: { start, end, @@ -161,7 +161,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns empty when transaction name is not found', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/transactions/groups/detailed_statistics`, query: { start, end, @@ -185,7 +185,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/detailed_statistics`, + pathname: `/internal/apm/services/opbeans-java/transactions/groups/detailed_statistics`, query: { numBuckets: 20, transactionType: 'request', diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts index ffdbf35992d7c..7664d28271e14 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.ts @@ -14,7 +14,7 @@ import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { registry } from '../../common/registry'; type TransactionsGroupsPrimaryStatistics = - APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/main_statistics'>; + APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics'>; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); @@ -29,7 +29,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/main_statistics`, + pathname: `/internal/apm/services/opbeans-java/transactions/groups/main_statistics`, query: { start, end, @@ -57,7 +57,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the correct data', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/main_statistics`, + pathname: `/internal/apm/services/opbeans-java/transactions/groups/main_statistics`, query: { start, end, @@ -132,7 +132,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the correct data for latency aggregation 99th percentile', async () => { const response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/transactions/groups/main_statistics`, + pathname: `/internal/apm/services/opbeans-java/transactions/groups/main_statistics`, query: { start, end, diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts b/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts index 34917528560bd..02820f85e8ee5 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts @@ -121,6 +121,11 @@ export default function (providerContext: FtrProviderContext) { lifecycle: { name: 'overridden by user', }, + mapping: { + total_fields: { + limit: '10000', + }, + }, number_of_shards: '3', }, }, diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts index e57899531e939..79f3d52821f75 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts @@ -414,6 +414,7 @@ const expectAssetsInstalled = ({ id: 'sample_dashboard', }); expect(resDashboard.id).equal('sample_dashboard'); + expect(resDashboard.references.map((ref: any) => ref.id).includes('sample_tag')).equal(true); const resDashboard2 = await kibanaServer.savedObjects.get({ type: 'dashboard', id: 'sample_dashboard2', @@ -444,6 +445,11 @@ const expectAssetsInstalled = ({ id: 'sample_security_rule', }); expect(resSecurityRule.id).equal('sample_security_rule'); + const resTag = await kibanaServer.savedObjects.get({ + type: 'tag', + id: 'sample_tag', + }); + expect(resTag.id).equal('sample_tag'); const resIndexPattern = await kibanaServer.savedObjects.get({ type: 'index-pattern', id: 'test-*', @@ -521,6 +527,10 @@ const expectAssetsInstalled = ({ id: 'sample_security_rule', type: 'security-rule', }, + { + id: 'sample_tag', + type: 'tag', + }, { id: 'sample_visualization', type: 'visualization', @@ -607,6 +617,7 @@ const expectAssetsInstalled = ({ { id: '4c758d70-ecf1-56b3-b704-6d8374841b34', type: 'epm-packages-assets' }, { id: 'e786cbd9-0f3b-5a0b-82a6-db25145ebf58', type: 'epm-packages-assets' }, { id: 'd8b175c3-0d42-5ec7-90c1-d1e4b307a4c2', type: 'epm-packages-assets' }, + { id: 'b265a5e0-c00b-5eda-ac44-2ddbd36d9ad0', type: 'epm-packages-assets' }, { id: '53c94591-aa33-591d-8200-cd524c2a0561', type: 'epm-packages-assets' }, { id: 'b658d2d4-752e-54b8-afc2-4c76155c1466', type: 'epm-packages-assets' }, ], diff --git a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts index a8ccb5e301d5b..5282312164148 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts @@ -220,6 +220,11 @@ export default function (providerContext: FtrProviderContext) { index: { lifecycle: { name: 'reference2' }, codec: 'best_compression', + mapping: { + total_fields: { + limit: '10000', + }, + }, query: { default_field: ['logs_test_name', 'new_field_name'], }, @@ -334,6 +339,10 @@ export default function (providerContext: FtrProviderContext) { id: 'sample_ml_module', type: 'ml-module', }, + { + id: 'sample_tag', + type: 'tag', + }, ], installed_es: [ { @@ -413,6 +422,7 @@ export default function (providerContext: FtrProviderContext) { { id: '4281a436-45a8-54ab-9724-fda6849f789d', type: 'epm-packages-assets' }, { id: '2e56f08b-1d06-55ed-abee-4708e1ccf0aa', type: 'epm-packages-assets' }, { id: '4035007b-9c33-5227-9803-2de8a17523b5', type: 'epm-packages-assets' }, + { id: 'e6ae7d31-6920-5408-9219-91ef1662044b', type: 'epm-packages-assets' }, { id: 'c7bf1a39-e057-58a0-afde-fb4b48751d8c', type: 'epm-packages-assets' }, { id: '8c665f28-a439-5f43-b5fd-8fda7b576735', type: 'epm-packages-assets' }, ], diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/dashboard/sample_dashboard.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/dashboard/sample_dashboard.json index 7f416c26cc9aa..c75dd7673dc38 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/dashboard/sample_dashboard.json +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/dashboard/sample_dashboard.json @@ -15,7 +15,8 @@ { "id": "sample_visualization", "name": "panel_0", "type": "visualization" }, { "id": "sample_search", "name": "panel_1", "type": "search" }, { "id": "sample_search", "name": "panel_2", "type": "search" }, - { "id": "sample_visualization", "name": "panel_3", "type": "visualization" } + { "id": "sample_visualization", "name": "panel_3", "type": "visualization" }, + { "id": "sample_tag", "type": "tag", "name": "tag-ref-sample_tag" } ], "id": "sample_dashboard", "type": "dashboard" diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/tag/sample_tag.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/tag/sample_tag.json new file mode 100644 index 0000000000000..c6494d42679b9 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/kibana/tag/sample_tag.json @@ -0,0 +1,17 @@ +{ + "id": "sample_tag", + "type": "tag", + "namespaces": [ + "default" + ], + "attributes": { + "name": "my tag", + "description": "", + "color": "#a80853" + }, + "references": [], + "migrationVersion": { + "tag": "8.0.0" + }, + "coreMigrationVersion": "8.0.0" +} \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/dashboard/sample_dashboard.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/dashboard/sample_dashboard.json index 4513c07f27786..1215a934c6368 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/dashboard/sample_dashboard.json +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/dashboard/sample_dashboard.json @@ -15,7 +15,8 @@ { "id": "sample_visualization", "name": "panel_0", "type": "visualization" }, { "id": "sample_search2", "name": "panel_1", "type": "search" }, { "id": "sample_search2", "name": "panel_2", "type": "search" }, - { "id": "sample_visualization", "name": "panel_3", "type": "visualization" } + { "id": "sample_visualization", "name": "panel_3", "type": "visualization" }, + { "id": "sample_tag", "type": "tag", "name": "tag-ref-sample_tag" } ], "id": "sample_dashboard", "type": "dashboard" diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/tag/sample_tag.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/tag/sample_tag.json new file mode 100644 index 0000000000000..c6494d42679b9 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/kibana/tag/sample_tag.json @@ -0,0 +1,17 @@ +{ + "id": "sample_tag", + "type": "tag", + "namespaces": [ + "default" + ], + "attributes": { + "name": "my tag", + "description": "", + "color": "#a80853" + }, + "references": [], + "migrationVersion": { + "tag": "8.0.0" + }, + "coreMigrationVersion": "8.0.0" +} \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml index c92f0ab5ae7f3..c473ce29b87d5 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml @@ -17,3 +17,4 @@ requirement: icons: - src: '/img/logo_overrides_64_color.svg' size: '16x16' + type: 'image/svg+xml' diff --git a/x-pack/test/functional/apps/discover/reporting.ts b/x-pack/test/functional/apps/discover/reporting.ts index 99bdb20580ce8..3c27aaeffa711 100644 --- a/x-pack/test/functional/apps/discover/reporting.ts +++ b/x-pack/test/functional/apps/discover/reporting.ts @@ -98,8 +98,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(res.text).to.be(`\n`); }); - // FIXME: https://github.com/elastic/kibana/issues/112186 - it.skip('generates a large export', async () => { + it('generates a large export', async () => { const fromTime = 'Apr 27, 2019 @ 23:56:51.374'; const toTime = 'Aug 23, 2019 @ 16:18:51.821'; await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); @@ -112,7 +111,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // match file length, the beginning and the end of the csv file contents const { text: csvFile } = await getReport(); - expect(csvFile.length).to.be(4954749); + expect(csvFile.length).to.be(5093456); expectSnapshot(csvFile.slice(0, 5000)).toMatch(); expectSnapshot(csvFile.slice(-5000)).toMatch(); }); diff --git a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts index c51e2968baee0..e2540d80280c2 100644 --- a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts +++ b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts @@ -16,7 +16,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const retry = getService('retry'); const esClient = getService('es'); - describe('Home page', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/114473 and https://github.com/elastic/kibana/issues/114474 + describe.skip('Home page', function () { before(async () => { await pageObjects.common.navigateToApp('indexLifecycleManagement'); }); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/custom_urls.ts b/x-pack/test/functional/apps/ml/anomaly_detection/custom_urls.ts index 0dcb767309608..7d4df75ccdcf7 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/custom_urls.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/custom_urls.ts @@ -81,7 +81,8 @@ export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); const browser = getService('browser'); - describe('custom urls', function () { + // FLAKY: https://github.com/elastic/kibana/issues/106053 + describe.skip('custom urls', function () { this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/trained_models.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/trained_models.ts index 43130651cb121..b302e0bfb1140 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/trained_models.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/trained_models.ts @@ -12,8 +12,8 @@ export default function ({ getService }: FtrProviderContext) { describe('trained models', function () { before(async () => { - await ml.trainedModels.createdTestTrainedModels('classification', 15); - await ml.trainedModels.createdTestTrainedModels('regression', 15); + await ml.trainedModels.createTestTrainedModels('classification', 15, true); + await ml.trainedModels.createTestTrainedModels('regression', 15); await ml.securityUI.loginAsMlPowerUser(); await ml.navigation.navigateToTrainedModels(); }); @@ -22,10 +22,116 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); + // 'Created at' will be different on each run, + // so we will just assert that the value is in the expected timestamp format. + const builtInModelData = { + modelId: 'lang_ident_model_1', + description: 'Model used for identifying language from arbitrary input text.', + modelTypes: ['classification', 'built-in'], + }; + + const modelWithPipelineData = { + modelId: 'dfa_classification_model_n_0', + description: '', + modelTypes: ['classification'], + }; + + const modelWithoutPipelineData = { + modelId: 'dfa_regression_model_n_0', + description: '', + modelTypes: ['regression'], + }; + it('renders trained models list', async () => { - await ml.trainedModels.assertRowsNumberPerPage(10); + await ml.testExecution.logTestStep( + 'should display the stats bar with the total number of models' + ); // +1 because of the built-in model await ml.trainedModels.assertStats(31); + + await ml.testExecution.logTestStep('should display the table'); + await ml.trainedModels.assertTableExists(); + await ml.trainedModels.assertRowsNumberPerPage(10); + }); + + it('displays the built-in model and no actions are enabled', async () => { + await ml.testExecution.logTestStep('should display the model in the table'); + await ml.trainedModelsTable.filterWithSearchString(builtInModelData.modelId, 1); + + await ml.testExecution.logTestStep('displays expected row values for the model in the table'); + await ml.trainedModelsTable.assertModelsRowFields(builtInModelData.modelId, { + id: builtInModelData.modelId, + description: builtInModelData.description, + modelTypes: builtInModelData.modelTypes, + }); + + await ml.testExecution.logTestStep( + 'should not show collapsed actions menu for the model in the table' + ); + await ml.trainedModelsTable.assertModelCollapsedActionsButtonExists( + builtInModelData.modelId, + false + ); + + await ml.testExecution.logTestStep( + 'should not show delete action for the model in the table' + ); + await ml.trainedModelsTable.assertModelDeleteActionButtonExists( + builtInModelData.modelId, + false + ); + }); + + it('displays a model with an ingest pipeline and delete action is disabled', async () => { + await ml.testExecution.logTestStep('should display the model in the table'); + await ml.trainedModelsTable.filterWithSearchString(modelWithPipelineData.modelId, 1); + + await ml.testExecution.logTestStep('displays expected row values for the model in the table'); + await ml.trainedModelsTable.assertModelsRowFields(modelWithPipelineData.modelId, { + id: modelWithPipelineData.modelId, + description: modelWithPipelineData.description, + modelTypes: modelWithPipelineData.modelTypes, + }); + + await ml.testExecution.logTestStep( + 'should show disabled delete action for the model in the table' + ); + + await ml.trainedModelsTable.assertModelDeleteActionButtonEnabled( + modelWithPipelineData.modelId, + false + ); + }); + + it('displays a model without an ingest pipeline and model can be deleted', async () => { + await ml.testExecution.logTestStep('should display the model in the table'); + await ml.trainedModelsTable.filterWithSearchString(modelWithoutPipelineData.modelId, 1); + + await ml.testExecution.logTestStep('displays expected row values for the model in the table'); + await ml.trainedModelsTable.assertModelsRowFields(modelWithoutPipelineData.modelId, { + id: modelWithoutPipelineData.modelId, + description: modelWithoutPipelineData.description, + modelTypes: modelWithoutPipelineData.modelTypes, + }); + + await ml.testExecution.logTestStep( + 'should show enabled delete action for the model in the table' + ); + + await ml.trainedModelsTable.assertModelDeleteActionButtonEnabled( + modelWithoutPipelineData.modelId, + true + ); + + await ml.testExecution.logTestStep('should show the delete modal'); + await ml.trainedModelsTable.clickDeleteAction(modelWithoutPipelineData.modelId); + + await ml.testExecution.logTestStep('should delete the model'); + await ml.trainedModelsTable.confirmDeleteModel(); + await ml.trainedModelsTable.assertModelDisplayedInTable( + modelWithoutPipelineData.modelId, + false + ); }); }); } diff --git a/x-pack/test/functional/apps/uptime/certificates.ts b/x-pack/test/functional/apps/uptime/certificates.ts index 610f07c183782..49298ec3a6f93 100644 --- a/x-pack/test/functional/apps/uptime/certificates.ts +++ b/x-pack/test/functional/apps/uptime/certificates.ts @@ -18,7 +18,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const es = getService('es'); - describe('certificates', function () { + // FLAKY https://github.com/elastic/kibana/issues/114261 + describe.skip('certificates', function () { describe('empty certificates', function () { before(async () => { await esArchiver.load(BLANK_INDEX_PATH); diff --git a/x-pack/test/functional/apps/visualize/reporting.ts b/x-pack/test/functional/apps/visualize/reporting.ts index efffa0b6a692b..07ce3d9b23128 100644 --- a/x-pack/test/functional/apps/visualize/reporting.ts +++ b/x-pack/test/functional/apps/visualize/reporting.ts @@ -25,13 +25,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'visEditor', ]); - // Failing: See https://github.com/elastic/kibana/issues/113496 - describe.skip('Visualize Reporting Screenshots', () => { + describe('Visualize Reporting Screenshots', () => { before('initialize tests', async () => { log.debug('ReportingPage:initTests'); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/reporting/ecommerce'); await kibanaServer.importExport.load(ecommerceSOPath); await browser.setWindowSize(1600, 850); + await kibanaServer.uiSettings.replace({ + 'timepicker:timeDefaults': + '{ "from": "2019-04-27T23:56:51.374Z", "to": "2019-08-23T16:18:51.821Z"}', + }); }); after('clean up archives', async () => { await esArchiver.unload('x-pack/test/functional/es_archives/reporting/ecommerce'); @@ -41,6 +44,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { refresh: true, body: { query: { match_all: {} } }, }); + await kibanaServer.uiSettings.unset('timepicker:timeDefaults'); }); describe('Print PDF button', () => { @@ -54,11 +58,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('becomes available when saved', async () => { - await PageObjects.timePicker.timePickerExists(); - const fromTime = 'Apr 27, 2019 @ 23:56:51.374'; - const toTime = 'Aug 23, 2019 @ 16:18:51.821'; - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - await PageObjects.visEditor.clickBucket('X-axis'); await PageObjects.visEditor.selectAggregation('Date Histogram'); await PageObjects.visEditor.clickGo(); diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index 7add4a024b469..abde3bf365384 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -981,11 +981,11 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { .expect(200) .then((res: any) => res.body); - log.debug('> Trained model crated'); + log.debug('> Trained model created'); return model; }, - async createdTestTrainedModels( + async createTestTrainedModels( modelType: ModelType, count: number = 10, withIngestPipelines = false diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index e12289c630696..d50ec371d7c23 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -51,6 +51,7 @@ import { SwimLaneProvider } from './swim_lane'; import { MachineLearningDashboardJobSelectionTableProvider } from './dashboard_job_selection_table'; import { MachineLearningDashboardEmbeddablesProvider } from './dashboard_embeddables'; import { TrainedModelsProvider } from './trained_models'; +import { TrainedModelsTableProvider } from './trained_models_table'; import { MachineLearningJobAnnotationsProvider } from './job_annotations_table'; export function MachineLearningProvider(context: FtrProviderContext) { @@ -121,6 +122,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { const alerting = MachineLearningAlertingProvider(context, commonUI); const swimLane = SwimLaneProvider(context); const trainedModels = TrainedModelsProvider(context, api, commonUI); + const trainedModelsTable = TrainedModelsTableProvider(context); return { anomaliesTable, @@ -168,5 +170,6 @@ export function MachineLearningProvider(context: FtrProviderContext) { testExecution, testResources, trainedModels, + trainedModelsTable, }; } diff --git a/x-pack/test/functional/services/ml/trained_models.ts b/x-pack/test/functional/services/ml/trained_models.ts index 7a1fa1714ca14..a15ec9fb1ecd6 100644 --- a/x-pack/test/functional/services/ml/trained_models.ts +++ b/x-pack/test/functional/services/ml/trained_models.ts @@ -18,15 +18,26 @@ export function TrainedModelsProvider( mlCommonUI: MlCommonUI ) { const testSubjects = getService('testSubjects'); + const retry = getService('retry'); return { - async createdTestTrainedModels(modelType: ModelType, count: number = 10) { - await mlApi.createdTestTrainedModels(modelType, count); + async createTestTrainedModels( + modelType: ModelType, + count: number = 10, + withIngestPipelines = false + ) { + await mlApi.createTestTrainedModels(modelType, count, withIngestPipelines); }, async assertStats(expectedTotalCount: number) { - const actualStats = await testSubjects.getVisibleText('mlInferenceModelsStatsBar'); - expect(actualStats).to.eql(`Total trained models: ${expectedTotalCount}`); + await retry.tryForTime(5 * 1000, async () => { + const actualStats = await testSubjects.getVisibleText('mlInferenceModelsStatsBar'); + expect(actualStats).to.eql(`Total trained models: ${expectedTotalCount}`); + }); + }, + + async assertTableExists() { + await testSubjects.existOrFail('~mlModelsTable'); }, async assertRowsNumberPerPage(rowsNumber: 10 | 25 | 100) { diff --git a/x-pack/test/functional/services/ml/trained_models_table.ts b/x-pack/test/functional/services/ml/trained_models_table.ts new file mode 100644 index 0000000000000..11a97a4fed8fe --- /dev/null +++ b/x-pack/test/functional/services/ml/trained_models_table.ts @@ -0,0 +1,212 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { ProvidedType } from '@kbn/test'; + +import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export interface TrainedModelRowData { + id: string; + description: string; + modelTypes: string[]; +} + +export type MlTrainedModelsTable = ProvidedType; + +export function TrainedModelsTableProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + return new (class ModelsTable { + public async parseModelsTable() { + const table = await testSubjects.find('~mlModelsTable'); + const $ = await table.parseDomContent(); + const rows = []; + + for (const tr of $.findTestSubjects('~mlModelsTableRow').toArray()) { + const $tr = $(tr); + + const $types = $tr.findTestSubjects('mlModelType'); + const modelTypes = []; + for (const el of $types.toArray()) { + modelTypes.push($(el).text().trim()); + } + + const rowObject: { + id: string; + description: string; + modelTypes: string[]; + createdAt: string; + } = { + id: $tr + .findTestSubject('mlModelsTableColumnId') + .find('.euiTableCellContent') + .text() + .trim(), + description: $tr + .findTestSubject('mlModelsTableColumnDescription') + .find('.euiTableCellContent') + .text() + .trim(), + modelTypes, + createdAt: $tr + .findTestSubject('mlModelsTableColumnCreatedAt') + .find('.euiTableCellContent') + .text() + .trim(), + }; + + rows.push(rowObject); + } + + return rows; + } + + public rowSelector(modelId: string, subSelector?: string) { + const row = `~mlModelsTable > ~row-${modelId}`; + return !subSelector ? row : `${row} > ${subSelector}`; + } + + public async waitForRefreshButtonLoaded() { + await testSubjects.existOrFail('~mlAnalyticsRefreshListButton', { timeout: 10 * 1000 }); + await testSubjects.existOrFail('mlAnalyticsRefreshListButton loaded', { timeout: 30 * 1000 }); + } + + public async refreshModelsTable() { + await this.waitForRefreshButtonLoaded(); + await testSubjects.click('~mlAnalyticsRefreshListButton'); + await this.waitForRefreshButtonLoaded(); + await this.waitForModelsToLoad(); + } + + public async waitForModelsToLoad() { + await testSubjects.existOrFail('~mlModelsTable', { timeout: 60 * 1000 }); + await testSubjects.existOrFail('mlModelsTable loaded', { timeout: 30 * 1000 }); + } + + async getModelsSearchInput(): Promise { + const tableListContainer = await testSubjects.find('mlModelsTableContainer'); + return await tableListContainer.findByClassName('euiFieldSearch'); + } + + public async assertModelsSearchInputValue(expectedSearchValue: string) { + const searchBarInput = await this.getModelsSearchInput(); + const actualSearchValue = await searchBarInput.getAttribute('value'); + expect(actualSearchValue).to.eql( + expectedSearchValue, + `Trained models search input value should be '${expectedSearchValue}' (got '${actualSearchValue}')` + ); + } + + public async filterWithSearchString(filter: string, expectedRowCount: number = 1) { + await this.waitForModelsToLoad(); + const searchBarInput = await this.getModelsSearchInput(); + await searchBarInput.clearValueWithKeyboard(); + await searchBarInput.type(filter); + await this.assertModelsSearchInputValue(filter); + + const rows = await this.parseModelsTable(); + const filteredRows = rows.filter((row) => row.id === filter); + expect(filteredRows).to.have.length( + expectedRowCount, + `Filtered trained models table should have ${expectedRowCount} row(s) for filter '${filter}' (got matching items '${filteredRows}')` + ); + } + + public async assertModelDisplayedInTable(modelId: string, shouldBeDisplayed: boolean) { + await retry.tryForTime(5 * 1000, async () => { + await this.filterWithSearchString(modelId, shouldBeDisplayed === true ? 1 : 0); + }); + } + + public async assertModelsRowFields(modelId: string, expectedRow: TrainedModelRowData) { + await this.refreshModelsTable(); + const rows = await this.parseModelsTable(); + const modelRow = rows.filter((row) => row.id === modelId)[0]; + expect(modelRow.id).to.eql( + expectedRow.id, + `Expected trained model row ID to be '${expectedRow.id}' (got '${modelRow.id}')` + ); + expect(modelRow.description).to.eql( + expectedRow.description, + `Expected trained model row description to be '${expectedRow.description}' (got '${modelRow.description}')` + ); + expect(modelRow.modelTypes.sort()).to.eql( + expectedRow.modelTypes.sort(), + `Expected trained model row types to be '${JSON.stringify( + expectedRow.modelTypes + )}' (got '${JSON.stringify(modelRow.modelTypes)}')` + ); + // 'Created at' will be different on each run, + // so we will just assert that the value is in the expected timestamp format. + expect(modelRow.createdAt).to.match( + /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/, + `Expected trained model row created at time to have same format as '2019-12-05 12:28:34' (got '${modelRow.createdAt}')` + ); + } + + public async assertModelCollapsedActionsButtonExists(modelId: string, expectedValue: boolean) { + const actionsExists = await testSubjects.exists( + this.rowSelector(modelId, 'euiCollapsedItemActionsButton') + ); + expect(actionsExists).to.eql( + expectedValue, + `Expected row collapsed actions menu button for trained model '${modelId}' to be ${ + expectedValue ? 'visible' : 'hidden' + } (got ${actionsExists ? 'visible' : 'hidden'})` + ); + } + + public async assertModelDeleteActionButtonExists(modelId: string, expectedValue: boolean) { + const actionsExists = await testSubjects.exists( + this.rowSelector(modelId, 'mlModelsTableRowDeleteAction') + ); + expect(actionsExists).to.eql( + expectedValue, + `Expected row delete action button for trained model '${modelId}' to be ${ + expectedValue ? 'visible' : 'hidden' + } (got ${actionsExists ? 'visible' : 'hidden'})` + ); + } + + public async assertModelDeleteActionButtonEnabled(modelId: string, expectedValue: boolean) { + await this.assertModelDeleteActionButtonExists(modelId, true); + const isEnabled = await testSubjects.isEnabled( + this.rowSelector(modelId, 'mlModelsTableRowDeleteAction') + ); + expect(isEnabled).to.eql( + expectedValue, + `Expected row delete action button for trained model '${modelId}' to be '${ + expectedValue ? 'enabled' : 'disabled' + }' (got '${isEnabled ? 'enabled' : 'disabled'}')` + ); + } + + public async assertDeleteModalExists() { + await testSubjects.existOrFail('mlModelsDeleteModal', { timeout: 60 * 1000 }); + } + + public async assertDeleteModalNotExists() { + await testSubjects.missingOrFail('mlModelsDeleteModal', { timeout: 60 * 1000 }); + } + + public async confirmDeleteModel() { + await retry.tryForTime(30 * 1000, async () => { + await this.assertDeleteModalExists(); + await testSubjects.click('mlModelsDeleteModalConfirmButton'); + await this.assertDeleteModalNotExists(); + }); + } + + public async clickDeleteAction(modelId: string) { + await testSubjects.click(this.rowSelector(modelId, 'mlModelsTableRowDeleteAction')); + await this.assertDeleteModalExists(); + } + })(); +} diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts index 2c8564df02e0b..fe734a764d2f3 100644 --- a/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts @@ -150,6 +150,21 @@ export default function ({ getService }: FtrProviderContext) { }, kibana: { saved_objects: [savedObject], + space_ids: ['space id'], + alert: { + rule: { + execution: { + uuid: '1fef0e1a-25ba-11ec-9621-0242ac130002', + status: 'succeeded', + status_order: 10, + metrics: { + total_indexing_duration_ms: 1000, + total_search_duration_ms: 2000, + execution_gap_duration_s: 3000, + }, + }, + }, + }, alerting: { instance_id: 'alert instance id', action_group_id: 'alert action group', diff --git a/x-pack/test/security_solution_cypress/es_archives/risky_hosts/data.json b/x-pack/test/security_solution_cypress/es_archives/risky_hosts/data.json index 7327f0fc76897..e42a13ab8d8a8 100644 --- a/x-pack/test/security_solution_cypress/es_archives/risky_hosts/data.json +++ b/x-pack/test/security_solution_cypress/es_archives/risky_hosts/data.json @@ -2,7 +2,7 @@ "type":"doc", "value":{ "id":"a4cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f", - "index":"ml_host_risk_score_latest", + "index":"ml_host_risk_score_latest_default", "source":{ "@timestamp":"2021-03-10T14:51:05.766Z", "risk_score":21, diff --git a/x-pack/test/security_solution_cypress/es_archives/risky_hosts/mappings.json b/x-pack/test/security_solution_cypress/es_archives/risky_hosts/mappings.json index 211c50f6baee2..f71c9cf8ed4c2 100644 --- a/x-pack/test/security_solution_cypress/es_archives/risky_hosts/mappings.json +++ b/x-pack/test/security_solution_cypress/es_archives/risky_hosts/mappings.json @@ -1,7 +1,7 @@ { "type": "index", "value": { - "index": "ml_host_risk_score_latest", + "index": "ml_host_risk_score_latest_default", "mappings": { "properties": { "@timestamp": { @@ -40,8 +40,8 @@ "settings": { "index": { "lifecycle": { - "name": "ml_host_risk_score_latest", - "rollover_alias": "ml_host_risk_score_latest" + "name": "ml_host_risk_score_latest_default", + "rollover_alias": "ml_host_risk_score_latest_default" }, "mapping": { "total_fields": { diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 70d60ba5c1b67..5ff206b8ad676 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -15,7 +15,7 @@ import { export default function (providerContext: FtrProviderContext) { const { loadTestFile, getService } = providerContext; - describe('endpoint', function () { + describe.skip('endpoint', function () { const ingestManager = getService('ingestManager'); const log = getService('log'); const endpointTestResources = getService('endpointTestResources'); diff --git a/yarn.lock b/yarn.lock index cbf8496857c8d..56331b12444ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -46,6 +46,13 @@ dependencies: "@babel/highlight" "^7.14.5" +"@babel/code-frame@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.15.8.tgz#45990c47adadb00c03677baa89221f7cc23d2503" + integrity sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg== + dependencies: + "@babel/highlight" "^7.14.5" + "@babel/compat-data@^7.12.5", "@babel/compat-data@^7.12.7": version "7.12.7" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.7.tgz#9329b4782a7d6bbd7eef57e11addf91ee3ef1e41" @@ -119,20 +126,20 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.15.5": - version "7.15.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.5.tgz#f8ed9ace730722544609f90c9bb49162dc3bf5b9" - integrity sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg== +"@babel/core@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.8.tgz#195b9f2bffe995d2c6c159e72fe525b4114e8c10" + integrity sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og== dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.15.4" + "@babel/code-frame" "^7.15.8" + "@babel/generator" "^7.15.8" "@babel/helper-compilation-targets" "^7.15.4" - "@babel/helper-module-transforms" "^7.15.4" + "@babel/helper-module-transforms" "^7.15.8" "@babel/helpers" "^7.15.4" - "@babel/parser" "^7.15.5" + "@babel/parser" "^7.15.8" "@babel/template" "^7.15.4" "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" + "@babel/types" "^7.15.6" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -140,10 +147,10 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/eslint-parser@^7.15.7": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.15.7.tgz#2dc3d0ff0ea22bb1e08d93b4eeb1149bf1c75f2d" - integrity sha512-yJkHyomClm6A2Xzb8pdAo4HzYMSXFn1O5zrCYvbFP0yQFvHueLedV8WiEno8yJOKStjUXzBZzJFeWQ7b3YMsqQ== +"@babel/eslint-parser@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.15.8.tgz#8988660b59d739500b67d0585fd4daca218d9f11" + integrity sha512-fYP7QFngCvgxjUuw8O057SVH5jCXsbFFOoE77CFDcvzwBVgTOkMD/L4mIC5Ud1xf8chK/no2fRbSSn1wvNmKuQ== dependencies: eslint-scope "^5.1.1" eslint-visitor-keys "^2.1.0" @@ -174,6 +181,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.8.tgz#fa56be6b596952ceb231048cf84ee499a19c0cd1" + integrity sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g== + dependencies: + "@babel/types" "^7.15.6" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.10": version "7.12.10" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz#54ab9b000e60a93644ce17b3f37d313aaf1d115d" @@ -405,6 +421,20 @@ "@babel/traverse" "^7.15.4" "@babel/types" "^7.15.6" +"@babel/helper-module-transforms@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz#d8c0e75a87a52e374a8f25f855174786a09498b2" + integrity sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg== + dependencies: + "@babel/helper-module-imports" "^7.15.4" + "@babel/helper-replace-supers" "^7.15.4" + "@babel/helper-simple-access" "^7.15.4" + "@babel/helper-split-export-declaration" "^7.15.4" + "@babel/helper-validator-identifier" "^7.15.7" + "@babel/template" "^7.15.4" + "@babel/traverse" "^7.15.4" + "@babel/types" "^7.15.6" + "@babel/helper-optimise-call-expression@^7.10.4", "@babel/helper-optimise-call-expression@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" @@ -602,11 +632,16 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.9.tgz#ca34cb95e1c2dd126863a84465ae8ef66114be99" integrity sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw== -"@babel/parser@^7.15.4", "@babel/parser@^7.15.5", "@babel/parser@^7.15.7": +"@babel/parser@^7.15.4": version "7.15.7" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.7.tgz#0c3ed4a2eb07b165dfa85b3cc45c727334c4edae" integrity sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g== +"@babel/parser@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.8.tgz#7bacdcbe71bdc3ff936d510c15dcea7cf0b99016" + integrity sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA== + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.15.4.tgz#dbdeabb1e80f622d9f0b583efb2999605e0a567e" @@ -625,10 +660,10 @@ "@babel/helper-remap-async-to-generator" "^7.12.1" "@babel/plugin-syntax-async-generators" "^7.8.0" -"@babel/plugin-proposal-async-generator-functions@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.15.4.tgz#f82aabe96c135d2ceaa917feb9f5fca31635277e" - integrity sha512-2zt2g5vTXpMC3OmK6uyjvdXptbhBXfA77XGrd3gh93zwG8lZYBLOBImiGBEG0RANu3JqKEACCz5CGk73OJROBw== +"@babel/plugin-proposal-async-generator-functions@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.15.8.tgz#a3100f785fab4357987c4223ab1b02b599048403" + integrity sha512-2Z5F2R2ibINTc63mY7FLqGfEbmofrHU9FitJW1Q7aPaKFhiPvSq6QEt/BoWN5oME3GVyjcRuNNSRbb9LC0CSWA== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/helper-remap-async-to-generator" "^7.15.4" @@ -1561,15 +1596,15 @@ resolve "^1.8.1" semver "^5.5.1" -"@babel/plugin-transform-runtime@^7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.15.0.tgz#d3aa650d11678ca76ce294071fda53d7804183b3" - integrity sha512-sfHYkLGjhzWTq6xsuQ01oEsUYjkHRux9fW1iUA68dC7Qd8BS1Unq4aZ8itmQp95zUzIcyR2EbNMTzAicFj+guw== +"@babel/plugin-transform-runtime@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.15.8.tgz#9d15b1e94e1c7f6344f65a8d573597d93c6cd886" + integrity sha512-+6zsde91jMzzvkzuEA3k63zCw+tm/GvuuabkpisgbDMTPQsIMHllE3XczJFFtEHLjjhKQFZmGQVRdELetlWpVw== dependencies: - "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-module-imports" "^7.15.4" "@babel/helper-plugin-utils" "^7.14.5" babel-plugin-polyfill-corejs2 "^0.2.2" - babel-plugin-polyfill-corejs3 "^0.2.2" + babel-plugin-polyfill-corejs3 "^0.2.5" babel-plugin-polyfill-regenerator "^0.2.2" semver "^6.3.0" @@ -1595,13 +1630,13 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" -"@babel/plugin-transform-spread@^7.14.6": - version "7.14.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz#6bd40e57fe7de94aa904851963b5616652f73144" - integrity sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag== +"@babel/plugin-transform-spread@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.15.8.tgz#79d5aa27f68d700449b2da07691dfa32d2f6d468" + integrity sha512-/daZ8s2tNaRekl9YJa9X4bzjpeRZLt122cpgFnQPLGUe61PH8zMEBmYqKkW5xF5JUEh5buEGXJoQpqBmIbpmEQ== dependencies: "@babel/helper-plugin-utils" "^7.14.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.15.4" "@babel/plugin-transform-sticky-regex@^7.12.7", "@babel/plugin-transform-sticky-regex@^7.2.0": version "7.12.7" @@ -1819,17 +1854,17 @@ core-js-compat "^3.8.0" semver "^5.5.0" -"@babel/preset-env@^7.15.6": - version "7.15.6" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.15.6.tgz#0f3898db9d63d320f21b17380d8462779de57659" - integrity sha512-L+6jcGn7EWu7zqaO2uoTDjjMBW+88FXzV8KvrBl2z6MtRNxlsmUNRlZPaNNPUTgqhyC5DHNFk/2Jmra+ublZWw== +"@babel/preset-env@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.15.8.tgz#f527ce5bcb121cd199f6b502bf23e420b3ff8dba" + integrity sha512-rCC0wH8husJgY4FPbHsiYyiLxSY8oMDJH7Rl6RQMknbN9oDDHhM9RDFvnGM2MgkbUJzSQB4gtuwygY5mCqGSsA== dependencies: "@babel/compat-data" "^7.15.0" "@babel/helper-compilation-targets" "^7.15.4" "@babel/helper-plugin-utils" "^7.14.5" "@babel/helper-validator-option" "^7.14.5" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.15.4" - "@babel/plugin-proposal-async-generator-functions" "^7.15.4" + "@babel/plugin-proposal-async-generator-functions" "^7.15.8" "@babel/plugin-proposal-class-properties" "^7.14.5" "@babel/plugin-proposal-class-static-block" "^7.15.4" "@babel/plugin-proposal-dynamic-import" "^7.14.5" @@ -1884,7 +1919,7 @@ "@babel/plugin-transform-regenerator" "^7.14.5" "@babel/plugin-transform-reserved-words" "^7.14.5" "@babel/plugin-transform-shorthand-properties" "^7.14.5" - "@babel/plugin-transform-spread" "^7.14.6" + "@babel/plugin-transform-spread" "^7.15.8" "@babel/plugin-transform-sticky-regex" "^7.14.5" "@babel/plugin-transform-template-literals" "^7.14.5" "@babel/plugin-transform-typeof-symbol" "^7.14.5" @@ -1893,7 +1928,7 @@ "@babel/preset-modules" "^0.1.4" "@babel/types" "^7.15.6" babel-plugin-polyfill-corejs2 "^0.2.2" - babel-plugin-polyfill-corejs3 "^0.2.2" + babel-plugin-polyfill-corejs3 "^0.2.5" babel-plugin-polyfill-regenerator "^0.2.2" core-js-compat "^3.16.0" semver "^6.3.0" @@ -2933,10 +2968,10 @@ "@hapi/validate" "1.x.x" "@hapi/wreck" "17.x.x" -"@hapi/hapi@^20.2.0": - version "20.2.0" - resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-20.2.0.tgz#bf0eca9cc591e83f3d72d06a998d31be35d044a1" - integrity sha512-yPH/z8KvlSLV8lI4EuId9z595fKKk5n6YA7H9UddWYWsBXMcnCyoFmHtYq0PCV4sNgKLD6QW9e27R9V9Z9aqqw== +"@hapi/hapi@^20.2.1": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-20.2.1.tgz#7482bc28757cb4671623a61bdb5ce920bffc8a2f" + integrity sha512-OXAU+yWLwkMfPFic+KITo+XPp6Oxpgc9WUH+pxXWcTIuvWbgco5TC/jS8UDvz+NFF5IzRgF2CL6UV/KLdQYUSQ== dependencies: "@hapi/accept" "^5.0.1" "@hapi/ammo" "^5.0.1" @@ -2966,11 +3001,16 @@ "@hapi/hoek" "9.x.x" "@hapi/validate" "1.x.x" -"@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.0.4", "@hapi/hoek@^9.2.0": +"@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.0.4": version "9.2.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== +"@hapi/hoek@^9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" + integrity sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw== + "@hapi/inert@^6.0.4": version "6.0.4" resolved "https://registry.yarnpkg.com/@hapi/inert/-/inert-6.0.4.tgz#0544221eabc457110a426818358d006e70ff1f41" @@ -8926,7 +8966,7 @@ babel-plugin-polyfill-corejs2@^0.2.2: "@babel/helper-define-polyfill-provider" "^0.2.2" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.2.2: +babel-plugin-polyfill-corejs3@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.5.tgz#2779846a16a1652244ae268b1e906ada107faf92" integrity sha512-ninF5MQNwAX9Z7c9ED+H2pGt1mXdP4TqzlHKyPIYmJIYz0N+++uwdM7RnJukklhzJ54Q84vA4ZJkgs7lu5vqcw==