diff --git a/.eslintignore b/.eslintignore
index 4b5e781c26971..d983c4bedfaab 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -26,6 +26,7 @@ target
/src/plugins/data/common/es_query/kuery/ast/_generated_/**
/src/plugins/vis_type_timelion/public/_generated_/**
/src/plugins/vis_type_timelion/public/webpackShims/jquery.flot.*
+/src/plugins/timelion/public/webpackShims/jquery.flot.*
/x-pack/legacy/plugins/**/__tests__/fixtures/**
/x-pack/plugins/apm/e2e/**/snapshots.js
/x-pack/plugins/apm/e2e/tmp/*
diff --git a/.i18nrc.json b/.i18nrc.json
index 9af7f17067b8e..e8431fdb3f0e1 100644
--- a/.i18nrc.json
+++ b/.i18nrc.json
@@ -44,7 +44,7 @@
"src/plugins/telemetry_management_section"
],
"tileMap": "src/plugins/tile_map",
- "timelion": ["src/legacy/core_plugins/timelion", "src/plugins/vis_type_timelion"],
+ "timelion": ["src/plugins/timelion", "src/plugins/vis_type_timelion"],
"uiActions": "src/plugins/ui_actions",
"visDefaultEditor": "src/plugins/vis_default_editor",
"visTypeMarkdown": "src/plugins/vis_type_markdown",
diff --git a/.sass-lint.yml b/.sass-lint.yml
index 56b85adca8a71..50cbe81cc7da2 100644
--- a/.sass-lint.yml
+++ b/.sass-lint.yml
@@ -1,7 +1,7 @@
files:
include:
- 'src/legacy/core_plugins/metrics/**/*.s+(a|c)ss'
- - 'src/legacy/core_plugins/timelion/**/*.s+(a|c)ss'
+ - 'src/plugins/timelion/**/*.s+(a|c)ss'
- 'src/plugins/vis_type_vislib/**/*.s+(a|c)ss'
- 'src/plugins/vis_type_xy/**/*.s+(a|c)ss'
- 'x-pack/plugins/canvas/**/*.s+(a|c)ss'
diff --git a/NOTICE.txt b/NOTICE.txt
index 94312d46c35ec..56280e6e3883e 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -147,6 +147,70 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+---
+Detection Rules
+Copyright 2020 Elasticsearch B.V.
+
+---
+This product bundles rules based on https://github.com/BlueTeamLabs/sentinel-attack
+which is available under a "MIT" license. The files based on this license are:
+
+- defense_evasion_via_filter_manager
+- discovery_process_discovery_via_tasklist_command
+- persistence_priv_escalation_via_accessibility_features
+- persistence_via_application_shimming
+- defense_evasion_execution_via_trusted_developer_utilities
+
+MIT License
+
+Copyright (c) 2019 Edoardo Gerosa, Olaf Hartong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+---
+This product bundles rules based on https://github.com/FSecureLABS/leonidas
+which is available under a "MIT" license. The files based on this license are:
+
+- credential_access_secretsmanager_getsecretvalue.toml
+
+MIT License
+
+Copyright (c) 2020 F-Secure LABS
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
---
This product bundles bootstrap@3.3.6 which is available under a
"MIT" license.
@@ -220,38 +284,6 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
----
-This product bundles rules based on https://github.com/BlueTeamLabs/sentinel-attack
-which is available under a "MIT" license. The files based on this license are:
-
-- windows_defense_evasion_via_filter_manager.json
-- windows_process_discovery_via_tasklist_command.json
-- windows_priv_escalation_via_accessibility_features.json
-- windows_persistence_via_application_shimming.json
-- windows_execution_via_trusted_developer_utilities.json
-
-MIT License
-
-Copyright (c) 2019 Edoardo Gerosa, Olaf Hartong
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
---
This product includes code that is adapted from mapbox-gl-js, which is
available under a "BSD-3-Clause" license.
diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md
index 48d4f929b6851..4ea7b04ebef6d 100644
--- a/STYLEGUIDE.md
+++ b/STYLEGUIDE.md
@@ -3,11 +3,18 @@
This guide applies to all development within the Kibana project and is
recommended for the development of all Kibana plugins.
+- [General](#general)
+- [HTML](#html)
+- [API endpoints](#api-endpoints)
+- [TypeScript/JavaScript](#typeScript/javaScript)
+- [SASS files](#sass-files)
+- [React](#react)
+
Besides the content in this style guide, the following style guides may also apply
to all development within the Kibana project. Please make sure to also read them:
-- [Accessibility style guide](https://elastic.github.io/eui/#/guidelines/accessibility)
-- [SASS style guide](https://elastic.github.io/eui/#/guidelines/sass)
+- [Accessibility style guide (EUI Docs)](https://elastic.github.io/eui/#/guidelines/accessibility)
+- [SASS style guide (EUI Docs)](https://elastic.github.io/eui/#/guidelines/sass)
## General
@@ -582,6 +589,39 @@ Do not use setters, they cause more problems than they can solve.
[sideeffect]: http://en.wikipedia.org/wiki/Side_effect_(computer_science)
+## SASS files
+
+When writing a new component, create a sibling SASS file of the same name and import directly into the **top** of the JS/TS component file. Doing so ensures the styles are never separated or lost on import and allows for better modularization (smaller individual plugin asset footprint).
+
+All SASS (.scss) files will automatically build with the [EUI](https://elastic.github.io/eui/#/guidelines/sass) & Kibana invisibles (SASS variables, mixins, functions) from the [`globals_[theme].scss` file](src/legacy/ui/public/styles/_globals_v7light.scss).
+
+While the styles for this component will only be loaded if the component exists on the page,
+the styles **will** be global and so it is recommended to use a three letter prefix on your
+classes to ensure proper scope.
+
+**Example:**
+
+```tsx
+// component.tsx
+
+import './component.scss';
+// All other imports below the SASS import
+
+export const Component = () => {
+ return (
+
+ );
+}
+```
+
+```scss
+// component.scss
+
+.plgComponent { ... }
+```
+
+Do not use the underscore `_` SASS file naming pattern when importing directly into a javascript file.
+
## React
The following style guide rules are specific for working with the React framework.
diff --git a/docs/developer/getting-started/index.asciidoc b/docs/developer/getting-started/index.asciidoc
index ff1623e22f1eb..47c4a52daf303 100644
--- a/docs/developer/getting-started/index.asciidoc
+++ b/docs/developer/getting-started/index.asciidoc
@@ -29,7 +29,7 @@ you can switch to the correct version when using nvm by running:
----
nvm use
----
-
+
Install the latest version of https://yarnpkg.com[yarn].
Bootstrap {kib} and install all the dependencies:
@@ -93,13 +93,13 @@ yarn es snapshot --license trial
`trial` will give you access to all capabilities.
-Read about more options for <>, like connecting to a remote host, running from source,
-preserving data inbetween runs, running remote cluster, etc.
+Read about more options for <>, like connecting to a remote host, running from source,
+preserving data inbetween runs, running remote cluster, etc.
[float]
=== Run {kib}
-In another terminal window, start up {kib}. Include developer examples by adding an optional `--run-examples` flag.
+In another terminal window, start up {kib}. Include developer examples by adding an optional `--run-examples` flag.
[source,bash]
----
@@ -125,8 +125,6 @@ cause the {kib} server to reboot.
* <>
-* <>
-
* <>
* <>
@@ -137,8 +135,6 @@ include::sample-data.asciidoc[]
include::debugging.asciidoc[]
-include::sass.asciidoc[]
-
include::building-kibana.asciidoc[]
include::development-plugin-resources.asciidoc[]
\ No newline at end of file
diff --git a/docs/developer/getting-started/sass.asciidoc b/docs/developer/getting-started/sass.asciidoc
deleted file mode 100644
index 194e001f642e1..0000000000000
--- a/docs/developer/getting-started/sass.asciidoc
+++ /dev/null
@@ -1,36 +0,0 @@
-[[kibana-sass]]
-=== Styling with SASS
-
-When writing a new component, create a sibling SASS file of the same
-name and import directly into the JS/TS component file. Doing so ensures
-the styles are never separated or lost on import and allows for better
-modularization (smaller individual plugin asset footprint).
-
-All SASS (.scss) files will automatically build with the
-https://elastic.github.io/eui/#/guidelines/sass[EUI] & {kib} invisibles (SASS variables, mixins, functions) from
-the {kib-repo}tree/{branch}/src/legacy/ui/public/styles/_globals_v7light.scss[globals_THEME.scss] file.
-
-*Example:*
-
-[source,tsx]
-----
-// component.tsx
-
-import './component.scss';
-
-export const Component = () => {
- return (
-
- );
-}
-----
-
-[source,scss]
-----
-// component.scss
-
-.plgComponent { ... }
-----
-
-Do not use the underscore `_` SASS file naming pattern when importing
-directly into a javascript file.
\ No newline at end of file
diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md
index 5f33d62382818..70ad235fb8971 100644
--- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md
+++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md
@@ -8,7 +8,7 @@
Signature:
```typescript
-export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions
+export interface SavedObjectsFindOptions
```
## Properties
@@ -19,6 +19,7 @@ export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions
| [fields](./kibana-plugin-core-public.savedobjectsfindoptions.fields.md) | string[] | An array of fields to include in the results |
| [filter](./kibana-plugin-core-public.savedobjectsfindoptions.filter.md) | string | |
| [hasReference](./kibana-plugin-core-public.savedobjectsfindoptions.hasreference.md) | { type: string; id: string; } | |
+| [namespaces](./kibana-plugin-core-public.savedobjectsfindoptions.namespaces.md) | string[] | |
| [page](./kibana-plugin-core-public.savedobjectsfindoptions.page.md) | number | |
| [perPage](./kibana-plugin-core-public.savedobjectsfindoptions.perpage.md) | number | |
| [preference](./kibana-plugin-core-public.savedobjectsfindoptions.preference.md) | string | An optional ES preference value to be used for the query \* |
diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.namespaces.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.namespaces.md
new file mode 100644
index 0000000000000..9cc9d64db1f65
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.namespaces.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsFindOptions](./kibana-plugin-core-public.savedobjectsfindoptions.md) > [namespaces](./kibana-plugin-core-public.savedobjectsfindoptions.namespaces.md)
+
+## SavedObjectsFindOptions.namespaces property
+
+Signature:
+
+```typescript
+namespaces?: string[];
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md
index b12983836d9e5..474dc6b7d6f28 100644
--- a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md
+++ b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md
@@ -88,8 +88,9 @@ async (context, request, response) => {
| [csp](./kibana-plugin-core-server.httpservicesetup.csp.md) | ICspConfig | The CSP config used for Kibana. |
| [getServerInfo](./kibana-plugin-core-server.httpservicesetup.getserverinfo.md) | () => HttpServerInfo | Provides common [information](./kibana-plugin-core-server.httpserverinfo.md) about the running http server. |
| [registerAuth](./kibana-plugin-core-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. |
-| [registerOnPostAuth](./kibana-plugin-core-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. |
-| [registerOnPreAuth](./kibana-plugin-core-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests. |
+| [registerOnPostAuth](./kibana-plugin-core-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic after Auth interceptor did make sure a user has access to the requested resource. |
+| [registerOnPreAuth](./kibana-plugin-core-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests before the Auth interceptor performs a check that user has access to requested resources. |
| [registerOnPreResponse](./kibana-plugin-core-server.httpservicesetup.registeronpreresponse.md) | (handler: OnPreResponseHandler) => void | To define custom logic to perform for the server response. |
+| [registerOnPreRouting](./kibana-plugin-core-server.httpservicesetup.registeronprerouting.md) | (handler: OnPreRoutingHandler) => void | To define custom logic to perform for incoming requests before server performs a route lookup. |
| [registerRouteHandlerContext](./kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md) | <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer | Register a context provider for a route handler. |
diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronpostauth.md b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronpostauth.md
index 01294693e282f..eff53b7b75fa5 100644
--- a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronpostauth.md
+++ b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronpostauth.md
@@ -4,7 +4,7 @@
## HttpServiceSetup.registerOnPostAuth property
-To define custom logic to perform for incoming requests.
+To define custom logic after Auth interceptor did make sure a user has access to the requested resource.
Signature:
@@ -14,5 +14,5 @@ registerOnPostAuth: (handler: OnPostAuthHandler) => void;
## Remarks
-Runs the handler after Auth interceptor did make sure a user has access to the requested resource. The auth state is available at stage via http.auth.get(..) Can register any number of registerOnPreAuth, which are called in sequence (from the first registered to the last). See [OnPostAuthHandler](./kibana-plugin-core-server.onpostauthhandler.md).
+The auth state is available at stage via http.auth.get(..) Can register any number of registerOnPreRouting, which are called in sequence (from the first registered to the last). See [OnPostAuthHandler](./kibana-plugin-core-server.onpostauthhandler.md).
diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronpreauth.md b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronpreauth.md
index f11453c8cda98..ce4cacb1c8749 100644
--- a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronpreauth.md
+++ b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronpreauth.md
@@ -4,7 +4,7 @@
## HttpServiceSetup.registerOnPreAuth property
-To define custom logic to perform for incoming requests.
+To define custom logic to perform for incoming requests before the Auth interceptor performs a check that user has access to requested resources.
Signature:
@@ -14,5 +14,5 @@ registerOnPreAuth: (handler: OnPreAuthHandler) => void;
## Remarks
-Runs the handler before Auth interceptor performs a check that user has access to requested resources, so it's the only place when you can forward a request to another URL right on the server. Can register any number of registerOnPostAuth, which are called in sequence (from the first registered to the last). See [OnPreAuthHandler](./kibana-plugin-core-server.onpreauthhandler.md).
+Can register any number of registerOnPostAuth, which are called in sequence (from the first registered to the last). See [OnPreRoutingHandler](./kibana-plugin-core-server.onpreroutinghandler.md).
diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronprerouting.md b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronprerouting.md
new file mode 100644
index 0000000000000..bdf5f15828669
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registeronprerouting.md
@@ -0,0 +1,18 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) > [registerOnPreRouting](./kibana-plugin-core-server.httpservicesetup.registeronprerouting.md)
+
+## HttpServiceSetup.registerOnPreRouting property
+
+To define custom logic to perform for incoming requests before server performs a route lookup.
+
+Signature:
+
+```typescript
+registerOnPreRouting: (handler: OnPreRoutingHandler) => void;
+```
+
+## Remarks
+
+It's the only place when you can forward a request to another URL right on the server. Can register any number of registerOnPreRouting, which are called in sequence (from the first registered to the last). See [OnPreRoutingHandler](./kibana-plugin-core-server.onpreroutinghandler.md).
+
diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md
index 8d4c0c915437e..a665327454c1a 100644
--- a/docs/development/core/server/kibana-plugin-core-server.md
+++ b/docs/development/core/server/kibana-plugin-core-server.md
@@ -122,7 +122,8 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [OnPreAuthToolkit](./kibana-plugin-core-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
| [OnPreResponseExtensions](./kibana-plugin-core-server.onpreresponseextensions.md) | Additional data to extend a response. |
| [OnPreResponseInfo](./kibana-plugin-core-server.onpreresponseinfo.md) | Response status code. |
-| [OnPreResponseToolkit](./kibana-plugin-core-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
+| [OnPreResponseToolkit](./kibana-plugin-core-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreRouting interceptor for incoming request. |
+| [OnPreRoutingToolkit](./kibana-plugin-core-server.onpreroutingtoolkit.md) | A tool set defining an outcome of OnPreRouting interceptor for incoming request. |
| [OpsMetrics](./kibana-plugin-core-server.opsmetrics.md) | Regroups metrics gathered by all the collectors. This contains metrics about the os/runtime, the kibana process and the http server. |
| [OpsOsMetrics](./kibana-plugin-core-server.opsosmetrics.md) | OS related metrics |
| [OpsProcessMetrics](./kibana-plugin-core-server.opsprocessmetrics.md) | Process related metrics |
@@ -256,7 +257,8 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [MutatingOperationRefreshSetting](./kibana-plugin-core-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation |
| [OnPostAuthHandler](./kibana-plugin-core-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-core-server.onpostauthtoolkit.md). |
| [OnPreAuthHandler](./kibana-plugin-core-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-core-server.onpreauthtoolkit.md). |
-| [OnPreResponseHandler](./kibana-plugin-core-server.onpreresponsehandler.md) | See [OnPreAuthToolkit](./kibana-plugin-core-server.onpreauthtoolkit.md). |
+| [OnPreResponseHandler](./kibana-plugin-core-server.onpreresponsehandler.md) | See [OnPreRoutingToolkit](./kibana-plugin-core-server.onpreroutingtoolkit.md). |
+| [OnPreRoutingHandler](./kibana-plugin-core-server.onpreroutinghandler.md) | See [OnPreRoutingToolkit](./kibana-plugin-core-server.onpreroutingtoolkit.md). |
| [PluginConfigSchema](./kibana-plugin-core-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. |
| [PluginInitializer](./kibana-plugin-core-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. |
| [PluginName](./kibana-plugin-core-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. |
diff --git a/docs/development/core/server/kibana-plugin-core-server.onpreauthtoolkit.md b/docs/development/core/server/kibana-plugin-core-server.onpreauthtoolkit.md
index 4097cb32c397a..8031dbc64fa6d 100644
--- a/docs/development/core/server/kibana-plugin-core-server.onpreauthtoolkit.md
+++ b/docs/development/core/server/kibana-plugin-core-server.onpreauthtoolkit.md
@@ -17,5 +17,4 @@ export interface OnPreAuthToolkit
| Property | Type | Description |
| --- | --- | --- |
| [next](./kibana-plugin-core-server.onpreauthtoolkit.next.md) | () => OnPreAuthResult | To pass request to the next handler |
-| [rewriteUrl](./kibana-plugin-core-server.onpreauthtoolkit.rewriteurl.md) | (url: string) => OnPreAuthResult | Rewrite requested resources url before is was authenticated and routed to a handler |
diff --git a/docs/development/core/server/kibana-plugin-core-server.onpreauthtoolkit.rewriteurl.md b/docs/development/core/server/kibana-plugin-core-server.onpreauthtoolkit.rewriteurl.md
deleted file mode 100644
index 7ecde62f88302..0000000000000
--- a/docs/development/core/server/kibana-plugin-core-server.onpreauthtoolkit.rewriteurl.md
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [OnPreAuthToolkit](./kibana-plugin-core-server.onpreauthtoolkit.md) > [rewriteUrl](./kibana-plugin-core-server.onpreauthtoolkit.rewriteurl.md)
-
-## OnPreAuthToolkit.rewriteUrl property
-
-Rewrite requested resources url before is was authenticated and routed to a handler
-
-Signature:
-
-```typescript
-rewriteUrl: (url: string) => OnPreAuthResult;
-```
diff --git a/docs/development/core/server/kibana-plugin-core-server.onpreresponsehandler.md b/docs/development/core/server/kibana-plugin-core-server.onpreresponsehandler.md
index e7eab8ee34d6f..10696fb79a2f6 100644
--- a/docs/development/core/server/kibana-plugin-core-server.onpreresponsehandler.md
+++ b/docs/development/core/server/kibana-plugin-core-server.onpreresponsehandler.md
@@ -4,7 +4,7 @@
## OnPreResponseHandler type
-See [OnPreAuthToolkit](./kibana-plugin-core-server.onpreauthtoolkit.md).
+See [OnPreRoutingToolkit](./kibana-plugin-core-server.onpreroutingtoolkit.md).
Signature:
diff --git a/docs/development/core/server/kibana-plugin-core-server.onpreresponsetoolkit.md b/docs/development/core/server/kibana-plugin-core-server.onpreresponsetoolkit.md
index 8e33e945b4ef9..306c375ba4a3c 100644
--- a/docs/development/core/server/kibana-plugin-core-server.onpreresponsetoolkit.md
+++ b/docs/development/core/server/kibana-plugin-core-server.onpreresponsetoolkit.md
@@ -4,7 +4,7 @@
## OnPreResponseToolkit interface
-A tool set defining an outcome of OnPreAuth interceptor for incoming request.
+A tool set defining an outcome of OnPreRouting interceptor for incoming request.
Signature:
diff --git a/docs/development/core/server/kibana-plugin-core-server.onpreroutinghandler.md b/docs/development/core/server/kibana-plugin-core-server.onpreroutinghandler.md
new file mode 100644
index 0000000000000..46016bcd5476a
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.onpreroutinghandler.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [OnPreRoutingHandler](./kibana-plugin-core-server.onpreroutinghandler.md)
+
+## OnPreRoutingHandler type
+
+See [OnPreRoutingToolkit](./kibana-plugin-core-server.onpreroutingtoolkit.md).
+
+Signature:
+
+```typescript
+export declare type OnPreRoutingHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPreRoutingToolkit) => OnPreRoutingResult | KibanaResponse | Promise;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.onpreroutingtoolkit.md b/docs/development/core/server/kibana-plugin-core-server.onpreroutingtoolkit.md
new file mode 100644
index 0000000000000..c564896b46a27
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.onpreroutingtoolkit.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [OnPreRoutingToolkit](./kibana-plugin-core-server.onpreroutingtoolkit.md)
+
+## OnPreRoutingToolkit interface
+
+A tool set defining an outcome of OnPreRouting interceptor for incoming request.
+
+Signature:
+
+```typescript
+export interface OnPreRoutingToolkit
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [next](./kibana-plugin-core-server.onpreroutingtoolkit.next.md) | () => OnPreRoutingResult | To pass request to the next handler |
+| [rewriteUrl](./kibana-plugin-core-server.onpreroutingtoolkit.rewriteurl.md) | (url: string) => OnPreRoutingResult | Rewrite requested resources url before is was authenticated and routed to a handler |
+
diff --git a/docs/development/core/server/kibana-plugin-core-server.onpreroutingtoolkit.next.md b/docs/development/core/server/kibana-plugin-core-server.onpreroutingtoolkit.next.md
new file mode 100644
index 0000000000000..7fb0b2ce67ba5
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.onpreroutingtoolkit.next.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [OnPreRoutingToolkit](./kibana-plugin-core-server.onpreroutingtoolkit.md) > [next](./kibana-plugin-core-server.onpreroutingtoolkit.next.md)
+
+## OnPreRoutingToolkit.next property
+
+To pass request to the next handler
+
+Signature:
+
+```typescript
+next: () => OnPreRoutingResult;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.onpreroutingtoolkit.rewriteurl.md b/docs/development/core/server/kibana-plugin-core-server.onpreroutingtoolkit.rewriteurl.md
new file mode 100644
index 0000000000000..346a12711c723
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.onpreroutingtoolkit.rewriteurl.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [OnPreRoutingToolkit](./kibana-plugin-core-server.onpreroutingtoolkit.md) > [rewriteUrl](./kibana-plugin-core-server.onpreroutingtoolkit.rewriteurl.md)
+
+## OnPreRoutingToolkit.rewriteUrl property
+
+Rewrite requested resources url before is was authenticated and routed to a handler
+
+Signature:
+
+```typescript
+rewriteUrl: (url: string) => OnPreRoutingResult;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md
index 6db16d979f1fe..67e931f0cb3b3 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md
@@ -8,7 +8,7 @@
Signature:
```typescript
-export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions
+export interface SavedObjectsFindOptions
```
## Properties
@@ -19,6 +19,7 @@ export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions
| [fields](./kibana-plugin-core-server.savedobjectsfindoptions.fields.md) | string[] | An array of fields to include in the results |
| [filter](./kibana-plugin-core-server.savedobjectsfindoptions.filter.md) | string | |
| [hasReference](./kibana-plugin-core-server.savedobjectsfindoptions.hasreference.md) | { type: string; id: string; } | |
+| [namespaces](./kibana-plugin-core-server.savedobjectsfindoptions.namespaces.md) | string[] | |
| [page](./kibana-plugin-core-server.savedobjectsfindoptions.page.md) | number | |
| [perPage](./kibana-plugin-core-server.savedobjectsfindoptions.perpage.md) | number | |
| [preference](./kibana-plugin-core-server.savedobjectsfindoptions.preference.md) | string | An optional ES preference value to be used for the query \* |
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.namespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.namespaces.md
new file mode 100644
index 0000000000000..cae707baa58c0
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.namespaces.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsFindOptions](./kibana-plugin-core-server.savedobjectsfindoptions.md) > [namespaces](./kibana-plugin-core-server.savedobjectsfindoptions.namespaces.md)
+
+## SavedObjectsFindOptions.namespaces property
+
+Signature:
+
+```typescript
+namespaces?: string[];
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md
index 8b89c802ec9ce..6c41441302c0b 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md
@@ -7,14 +7,14 @@
Signature:
```typescript
-find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, preference, }: SavedObjectsFindOptions): Promise>;
+find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, }: SavedObjectsFindOptions): Promise>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
-| { search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, preference, } | SavedObjectsFindOptions | |
+| { search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, } | SavedObjectsFindOptions | |
Returns:
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md
index b9a92561f29fb..5b02707a3c0f4 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md
@@ -23,7 +23,7 @@ export declare class SavedObjectsRepository
| [delete(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.delete.md) | | Deletes an object |
| [deleteByNamespace(namespace, options)](./kibana-plugin-core-server.savedobjectsrepository.deletebynamespace.md) | | Deletes all objects from the provided namespace. |
| [deleteFromNamespaces(type, id, namespaces, options)](./kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md) | | Removes one or more namespaces from a given multi-namespace saved object. If no namespaces remain, the saved object is deleted entirely. This method and \[addToNamespaces\][SavedObjectsRepository.addToNamespaces()](./kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md) are the only ways to change which Spaces a multi-namespace saved object is shared to. |
-| [find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, preference, })](./kibana-plugin-core-server.savedobjectsrepository.find.md) | | |
+| [find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, })](./kibana-plugin-core-server.savedobjectsrepository.find.md) | | |
| [get(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.get.md) | | Gets a single object |
| [incrementCounter(type, id, counterFieldName, options)](./kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md) | | Increases a counter field by one. Creates the document if one doesn't exist for the given id. |
| [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsrepository.update.md) | | Updates an object |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.baseformatterspublic.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.baseformatterspublic.md
index ddbf1a8459d1f..25f046983cbce 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.baseformatterspublic.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.baseformatterspublic.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-baseFormattersPublic: (import("../../common").FieldFormatInstanceType | typeof DateFormat)[]
+baseFormattersPublic: (import("../../common").FieldFormatInstanceType | typeof DateNanosFormat | typeof DateFormat)[]
```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md
index 45fc1a608e8ca..0dddc65f4db92 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md
@@ -13,7 +13,6 @@ fieldFormats: {
BoolFormat: typeof BoolFormat;
BytesFormat: typeof BytesFormat;
ColorFormat: typeof ColorFormat;
- DateNanosFormat: typeof DateNanosFormat;
DurationFormat: typeof DurationFormat;
IpFormat: typeof IpFormat;
NumberFormat: typeof NumberFormat;
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.irequesttypesmap.es.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.irequesttypesmap.es.md
deleted file mode 100644
index 9cebff05dc9db..0000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.irequesttypesmap.es.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IRequestTypesMap](./kibana-plugin-plugins-data-server.irequesttypesmap.md) > [es](./kibana-plugin-plugins-data-server.irequesttypesmap.es.md)
-
-## IRequestTypesMap.es property
-
-Signature:
-
-```typescript
-[ES_SEARCH_STRATEGY]: IEsSearchRequest;
-```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.irequesttypesmap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.irequesttypesmap.md
deleted file mode 100644
index 3f5e4ba0f7799..0000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.irequesttypesmap.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IRequestTypesMap](./kibana-plugin-plugins-data-server.irequesttypesmap.md)
-
-## IRequestTypesMap interface
-
-The map of search strategy IDs to the corresponding request type definitions.
-
-Signature:
-
-```typescript
-export interface IRequestTypesMap
-```
-
-## Properties
-
-| Property | Type | Description |
-| --- | --- | --- |
-| [es](./kibana-plugin-plugins-data-server.irequesttypesmap.es.md) | IEsSearchRequest | |
-
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iresponsetypesmap.es.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iresponsetypesmap.es.md
deleted file mode 100644
index 1154fc141d6c7..0000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iresponsetypesmap.es.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IResponseTypesMap](./kibana-plugin-plugins-data-server.iresponsetypesmap.md) > [es](./kibana-plugin-plugins-data-server.iresponsetypesmap.es.md)
-
-## IResponseTypesMap.es property
-
-Signature:
-
-```typescript
-[ES_SEARCH_STRATEGY]: IEsSearchResponse;
-```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iresponsetypesmap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iresponsetypesmap.md
deleted file mode 100644
index 629ab4347eda8..0000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iresponsetypesmap.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IResponseTypesMap](./kibana-plugin-plugins-data-server.iresponsetypesmap.md)
-
-## IResponseTypesMap interface
-
-The map of search strategy IDs to the corresponding response type definitions.
-
-Signature:
-
-```typescript
-export interface IResponseTypesMap
-```
-
-## Properties
-
-| Property | Type | Description |
-| --- | --- | --- |
-| [es](./kibana-plugin-plugins-data-server.iresponsetypesmap.es.md) | IEsSearchResponse | |
-
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearch.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearch.md
deleted file mode 100644
index 96991579c1716..0000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearch.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearch](./kibana-plugin-plugins-data-server.isearch.md)
-
-## ISearch type
-
-Signature:
-
-```typescript
-export declare type ISearch = (context: RequestHandlerContext, request: IRequestTypesMap[T], options?: ISearchOptions) => Promise;
-```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcancel.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcancel.md
deleted file mode 100644
index b5a687d1b19d8..0000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcancel.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchCancel](./kibana-plugin-plugins-data-server.isearchcancel.md)
-
-## ISearchCancel type
-
-Signature:
-
-```typescript
-export declare type ISearchCancel = (context: RequestHandlerContext, id: string) => Promise;
-```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md
index 49412fc42d3b5..002ce864a1aa4 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md
@@ -15,4 +15,5 @@ export interface ISearchOptions
| Property | Type | Description |
| --- | --- | --- |
| [signal](./kibana-plugin-plugins-data-server.isearchoptions.signal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. |
+| [strategy](./kibana-plugin-plugins-data-server.isearchoptions.strategy.md) | string | |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md
new file mode 100644
index 0000000000000..6df72d023e2c0
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [strategy](./kibana-plugin-plugins-data-server.isearchoptions.strategy.md)
+
+## ISearchOptions.strategy property
+
+Signature:
+
+```typescript
+strategy?: string;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md
index 93e253b2e98a3..ca8ad8fdc06ea 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md
@@ -14,5 +14,5 @@ export interface ISearchSetup
| Property | Type | Description |
| --- | --- | --- |
-| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | TRegisterSearchStrategy | Extension point exposed for other plugins to register their own search strategies. |
+| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | (name: string, strategy: ISearchStrategy) => void | Extension point exposed for other plugins to register their own search strategies. |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md
index c06b8b00806bf..73c575e7095ed 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md
@@ -9,5 +9,5 @@ Extension point exposed for other plugins to register their own search strategie
Signature:
```typescript
-registerSearchStrategy: TRegisterSearchStrategy;
+registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void;
```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md
index 0ba4bf578d6cc..970b2811a574b 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md
@@ -9,5 +9,5 @@ Get other registered search strategies. For example, if a new strategy needs to
Signature:
```typescript
-getSearchStrategy: TGetSearchStrategy;
+getSearchStrategy: (name: string) => ISearchStrategy;
```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md
index abe72396f61e1..308ce3cb568bc 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md
@@ -14,5 +14,6 @@ export interface ISearchStart
| Property | Type | Description |
| --- | --- | --- |
-| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | TGetSearchStrategy | Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. |
+| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | (name: string) => ISearchStrategy | Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. |
+| [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) | (context: RequestHandlerContext, request: IKibanaSearchRequest, options: ISearchOptions) => Promise<IKibanaSearchResponse> | |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md
new file mode 100644
index 0000000000000..1c2ae91699559
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) > [search](./kibana-plugin-plugins-data-server.isearchstart.search.md)
+
+## ISearchStart.search property
+
+Signature:
+
+```typescript
+search: (context: RequestHandlerContext, request: IKibanaSearchRequest, options: ISearchOptions) => Promise;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md
index c1e0c3d9f2330..34903697090ea 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-cancel?: ISearchCancel;
+cancel?: (context: RequestHandlerContext, id: string) => Promise;
```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md
index 167c6ab6e5a16..d54e027c4b847 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md
@@ -9,13 +9,13 @@ Search strategy interface contains a search method that takes in a request and r
Signature:
```typescript
-export interface ISearchStrategy
+export interface ISearchStrategy
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
-| [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md) | ISearchCancel<T> | |
-| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | ISearch<T> | |
+| [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md) | (context: RequestHandlerContext, id: string) => Promise<void> | |
+| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | (context: RequestHandlerContext, request: IEsSearchRequest, options?: ISearchOptions) => Promise<IEsSearchResponse> | |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md
index 34a17ca87807a..1a225d0c9aeab 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-search: ISearch;
+search: (context: RequestHandlerContext, request: IEsSearchRequest, options?: ISearchOptions) => Promise;
```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md
index c80112fb17dde..9adefda718338 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md
@@ -40,8 +40,6 @@
| [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) | |
| [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | Use data plugin interface instead |
| [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) | |
-| [IRequestTypesMap](./kibana-plugin-plugins-data-server.irequesttypesmap.md) | The map of search strategy IDs to the corresponding request type definitions. |
-| [IResponseTypesMap](./kibana-plugin-plugins-data-server.iresponsetypesmap.md) | The map of search strategy IDs to the corresponding response type definitions. |
| [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) | |
| [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) | |
| [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | |
@@ -73,8 +71,5 @@
| --- | --- |
| [FieldFormatsGetConfigFn](./kibana-plugin-plugins-data-server.fieldformatsgetconfigfn.md) | |
| [IFieldFormatsRegistry](./kibana-plugin-plugins-data-server.ifieldformatsregistry.md) | |
-| [ISearch](./kibana-plugin-plugins-data-server.isearch.md) | |
-| [ISearchCancel](./kibana-plugin-plugins-data-server.isearchcancel.md) | |
| [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | |
-| [TStrategyTypes](./kibana-plugin-plugins-data-server.tstrategytypes.md) | Contains all known strategy type identifiers that will be used to map to request and response shapes. Plugins that wish to add their own custom search strategies should extend this type via:const MY\_STRATEGY = 'MY\_STRATEGY';declare module 'src/plugins/search/server' { export interface IRequestTypesMap { \[MY\_STRATEGY\]: IMySearchRequest; }export interface IResponseTypesMap { \[MY\_STRATEGY\]: IMySearchResponse } } |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tstrategytypes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tstrategytypes.md
deleted file mode 100644
index 443d8d1b424d0..0000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tstrategytypes.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TStrategyTypes](./kibana-plugin-plugins-data-server.tstrategytypes.md)
-
-## TStrategyTypes type
-
-Contains all known strategy type identifiers that will be used to map to request and response shapes. Plugins that wish to add their own custom search strategies should extend this type via:
-
-const MY\_STRATEGY = 'MY\_STRATEGY';
-
-declare module 'src/plugins/search/server' { export interface IRequestTypesMap { \[MY\_STRATEGY\]: IMySearchRequest; }
-
-export interface IResponseTypesMap { \[MY\_STRATEGY\]: IMySearchResponse } }
-
-Signature:
-
-```typescript
-export declare type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string;
-```
diff --git a/docs/settings/ingest-manager-settings.asciidoc b/docs/settings/ingest-manager-settings.asciidoc
index f46c769079040..30e11f726c26b 100644
--- a/docs/settings/ingest-manager-settings.asciidoc
+++ b/docs/settings/ingest-manager-settings.asciidoc
@@ -8,8 +8,7 @@
experimental[]
You can configure `xpack.ingestManager` settings in your `kibana.yml`.
-By default, {ingest-manager} is not enabled. You need to
-enable it. To use {fleet}, you also need to configure {kib} and {es} hosts.
+By default, {ingest-manager} is enabled. To use {fleet}, you also need to configure {kib} and {es} hosts.
See the {ingest-guide}/index.html[Ingest Management] docs for more information.
@@ -19,9 +18,7 @@ See the {ingest-guide}/index.html[Ingest Management] docs for more information.
[cols="2*<"]
|===
| `xpack.ingestManager.enabled` {ess-icon}
- | Set to `true` to enable {ingest-manager}.
-| `xpack.ingestManager.epm.enabled` {ess-icon}
- | Set to `true` (default) to enable {package-manager}.
+ | Set to `true` (default) to enable {ingest-manager}.
| `xpack.ingestManager.fleet.enabled` {ess-icon}
| Set to `true` (default) to enable {fleet}.
|===
@@ -32,7 +29,7 @@ See the {ingest-guide}/index.html[Ingest Management] docs for more information.
[cols="2*<"]
|===
-| `xpack.ingestManager.epm.registryUrl`
+| `xpack.ingestManager.registryUrl`
| The address to use to reach {package-manager} registry.
|===
diff --git a/docs/user/alerting/action-types/email.asciidoc b/docs/user/alerting/action-types/email.asciidoc
index 4fb8a816d1ec9..f6a02b9038c02 100644
--- a/docs/user/alerting/action-types/email.asciidoc
+++ b/docs/user/alerting/action-types/email.asciidoc
@@ -77,3 +77,122 @@ Email actions have the following configuration properties:
To, CC, BCC:: Each is a list of addresses. Addresses can be specified in `user@host-name` format, or in `name ` format. One of To, CC, or BCC must contain an entry.
Subject:: The subject line of the email.
Message:: The message text of the email. Markdown format is supported.
+
+[[configuring-email]]
+==== Configuring email accounts
+
+The email action can send email using many popular SMTP email services.
+
+You configure the email action to send emails using the connector form.
+For more information about configuring the email connector to work with different email
+systems, refer to:
+
+* <>
+* <>
+* <>
+* <>
+
+[float]
+[[gmail]]
+===== Sending email from Gmail
+
+Use the following email account settings to send email from the
+https://mail.google.com[Gmail] SMTP service:
+
+[source,text]
+--------------------------------------------------
+ config:
+ host: smtp.gmail.com
+ port: 465
+ secure: true
+ secrets:
+ user:
+ password:
+--------------------------------------------------
+// CONSOLE
+
+If you get an authentication error that indicates that you need to continue the
+sign-in process from a web browser when the action attempts to send email, you need
+to configure Gmail to https://support.google.com/accounts/answer/6010255?hl=en[allow
+less secure apps to access your account].
+
+If two-step verification is enabled for your account, you must generate and use
+a unique App Password to send email from {watcher}. See
+https://support.google.com/accounts/answer/185833?hl=en[Sign in using App Passwords]
+for more information.
+
+[float]
+[[outlook]]
+===== Sending email from Outlook.com
+
+Use the following email account settings to send email action from the
+https://www.outlook.com/[Outlook.com] SMTP service:
+
+[source,text]
+--------------------------------------------------
+config:
+ host: smtp-mail.outlook.com
+ port: 465
+ secure: true
+secrets:
+ user:
+ password:
+--------------------------------------------------
+
+When sending emails, you must provide a from address, either as the default
+in your account configuration or as part of the email action in the watch.
+
+NOTE: You must use a unique App Password if two-step verification is enabled.
+ See http://windows.microsoft.com/en-us/windows/app-passwords-two-step-verification[App
+ passwords and two-step verification] for more information.
+
+[float]
+[[amazon-ses]]
+===== Sending email from Amazon SES (Simple Email Service)
+
+Use the following email account settings to send email from the
+http://aws.amazon.com/ses[Amazon Simple Email Service] (SES) SMTP service:
+
+[source,text]
+--------------------------------------------------
+config:
+ host: email-smtp.us-east-1.amazonaws.com <1>
+ port: 465
+ secure: true
+secrets:
+ user:
+ password:
+--------------------------------------------------
+<1> `smtp.host` varies depending on the region
+
+NOTE: You must use your Amazon SES SMTP credentials to send email through
+ Amazon SES. For more information, see
+ http://docs.aws.amazon.com/ses/latest/DeveloperGuide/smtp-credentials.html[Obtaining
+ Your Amazon SES SMTP Credentials]. You might also need to verify
+ https://docs.aws.amazon.com/ses/latest/DeveloperGuide/verify-email-addresses.html[your email address]
+ or https://docs.aws.amazon.com/ses/latest/DeveloperGuide/verify-domains.html[your whole domain]
+ at AWS.
+
+[float]
+[[exchange]]
+===== Sending email from Microsoft Exchange
+
+Use the following email account settings to send email action from Microsoft
+Exchange:
+
+[source,text]
+--------------------------------------------------
+config:
+ host:
+ port: 465
+ secure: true
+ from: <1>
+secrets:
+ user: <2>
+ password:
+--------------------------------------------------
+<1> Some organizations configure Exchange to validate that the `from` field is a
+ valid local email account.
+<2> Many organizations support use of your email address as your username.
+ Check with your system administrator if you receive
+ authentication-related failures.
diff --git a/docs/user/alerting/action-types/index.asciidoc b/docs/user/alerting/action-types/index.asciidoc
index 115423086bae3..3a57c44494394 100644
--- a/docs/user/alerting/action-types/index.asciidoc
+++ b/docs/user/alerting/action-types/index.asciidoc
@@ -2,7 +2,7 @@
[[index-action-type]]
=== Index action
-The index action type will index a document into {es}.
+The index action type will index a document into {es}. See also the {ref}/indices-create-index.html[create index API].
[float]
[[index-connector-configuration]]
@@ -53,4 +53,38 @@ Execution time field:: This field will be automatically set to the time the ale
Index actions have the following properties:
-Document:: The document to index in json format.
+Document:: The document to index in JSON format.
+
+Example of the index document for Index Threshold alert:
+
+[source,text]
+--------------------------------------------------
+{
+ "alert_id": "{{alertId}}",
+ "alert_name": "{{alertName}}",
+ "alert_instance_id": "{{alertInstanceId}}",
+ "context_message": "{{context.message}}"
+}
+--------------------------------------------------
+
+Example of create test index using the API.
+
+[source,text]
+--------------------------------------------------
+PUT test
+{
+ "settings" : {
+ "number_of_shards" : 1
+ },
+ "mappings" : {
+ "_doc" : {
+ "properties" : {
+ "alert_id" : { "type" : "text" },
+ "alert_name" : { "type" : "text" },
+ "alert_instance_id" : { "type" : "text" },
+ "context_message": { "type" : "text" }
+ }
+ }
+ }
+}
+--------------------------------------------------
diff --git a/docs/user/alerting/action-types/slack.asciidoc b/docs/user/alerting/action-types/slack.asciidoc
index 5bad8a53f898c..99bf73c0f5597 100644
--- a/docs/user/alerting/action-types/slack.asciidoc
+++ b/docs/user/alerting/action-types/slack.asciidoc
@@ -38,3 +38,23 @@ Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messa
Slack actions have the following properties:
Message:: The message text, converted to the `text` field in the Webhook JSON payload. Currently only the text field is supported. Markdown, images, and other advanced formatting are not yet supported.
+
+[[configuring-slack]]
+==== Configuring Slack Accounts
+
+You configure the accounts Slack action type can use to communicate with Slack in the
+connector form.
+
+You need a https://api.slack.com/incoming-webhooks[Slack webhook URL] to
+configure a Slack account. To create a webhook
+URL, set up an an **Incoming Webhook Integration** through the Slack console:
+
+. Log in to http://slack.com[slack.com] as a team administrator.
+. Go to https://my.slack.com/services/new/incoming-webhook.
+. Select a default channel for the integration.
++
+image::images/slack-add-webhook-integration.png[]
+. Click *Add Incoming Webhook Integration*.
+. Copy the generated webhook URL so you can paste it into your Slack connector form.
++
+image::images/slack-copy-webhook-url.png[]
diff --git a/docs/user/alerting/images/slack-add-webhook-integration.png b/docs/user/alerting/images/slack-add-webhook-integration.png
new file mode 100644
index 0000000000000..347822ddd9fac
Binary files /dev/null and b/docs/user/alerting/images/slack-add-webhook-integration.png differ
diff --git a/docs/user/alerting/images/slack-copy-webhook-url.png b/docs/user/alerting/images/slack-copy-webhook-url.png
new file mode 100644
index 0000000000000..0acc9488e22a3
Binary files /dev/null and b/docs/user/alerting/images/slack-copy-webhook-url.png differ
diff --git a/package.json b/package.json
index 7ab6bfb91a376..55a099b4e5c0c 100644
--- a/package.json
+++ b/package.json
@@ -87,7 +87,6 @@
"**/@types/hoist-non-react-statics": "^3.3.1",
"**/@types/chai": "^4.2.11",
"**/cypress/@types/lodash": "^4.14.155",
- "**/cypress/lodash": "^4.15.19",
"**/typescript": "3.9.5",
"**/graphql-toolkit/lodash": "^4.17.15",
"**/hoist-non-react-statics": "^3.3.2",
diff --git a/packages/kbn-config-schema/src/types/string_type.ts b/packages/kbn-config-schema/src/types/string_type.ts
index cb780bcbbc6bd..c7d386df7c3ba 100644
--- a/packages/kbn-config-schema/src/types/string_type.ts
+++ b/packages/kbn-config-schema/src/types/string_type.ts
@@ -29,8 +29,8 @@ export type StringOptions = TypeOptions & {
export class StringType extends Type {
constructor(options: StringOptions = {}) {
- // We want to allow empty strings, however calling `allow('')` casues
- // Joi to whitelist the value and skip any additional validation.
+ // We want to allow empty strings, however calling `allow('')` causes
+ // Joi to allow the value and skip any additional validation.
// Instead, we reimplement the string validator manually except in the
// hostname case where empty strings aren't allowed anyways.
let schema =
diff --git a/packages/kbn-dev-utils/src/run/run.ts b/packages/kbn-dev-utils/src/run/run.ts
index 894db0d3fdadb..029d428565163 100644
--- a/packages/kbn-dev-utils/src/run/run.ts
+++ b/packages/kbn-dev-utils/src/run/run.ts
@@ -22,7 +22,7 @@ import { inspect } from 'util';
// @ts-ignore @types are outdated and module is super simple
import exitHook from 'exit-hook';
-import { pickLevelFromFlags, ToolingLog } from '../tooling_log';
+import { pickLevelFromFlags, ToolingLog, LogLevel } from '../tooling_log';
import { createFlagError, isFailError } from './fail';
import { Flags, getFlags, getHelp } from './flags';
import { ProcRunner, withProcRunner } from '../proc_runner';
@@ -38,6 +38,9 @@ type RunFn = (args: {
export interface Options {
usage?: string;
description?: string;
+ log?: {
+ defaultLevel?: LogLevel;
+ };
flags?: {
allowUnexpected?: boolean;
guessTypesForUnexpectedFlags?: boolean;
@@ -58,7 +61,9 @@ export async function run(fn: RunFn, options: Options = {}) {
}
const log = new ToolingLog({
- level: pickLevelFromFlags(flags),
+ level: pickLevelFromFlags(flags, {
+ default: options.log?.defaultLevel,
+ }),
writeTo: process.stdout,
});
diff --git a/packages/kbn-test/src/failed_tests_reporter/README.md b/packages/kbn-test/src/failed_tests_reporter/README.md
index 20592ecd733b6..0473ae7357def 100644
--- a/packages/kbn-test/src/failed_tests_reporter/README.md
+++ b/packages/kbn-test/src/failed_tests_reporter/README.md
@@ -7,15 +7,15 @@ A little CLI that runs in CI to find the failed tests in the JUnit reports, then
To fetch some JUnit reports from a recent build on CI, visit its `Google Cloud Storage Upload Report` and execute the following in the JS Console:
```js
-copy(`wget "${Array.from($$('a[href$=".xml"]')).filter(a => a.innerText === 'Download').map(a => a.href.replace('https://storage.cloud.google.com/', 'https://storage.googleapis.com/')).join('" "')}"`)
+copy(`wget -x -nH --cut-dirs 5 -P "target/downloaded_junit" "${Array.from($$('a[href$=".xml"]')).filter(a => a.innerText === 'Download').map(a => a.href.replace('https://storage.cloud.google.com/', 'https://storage.googleapis.com/')).join('" "')}"`)
```
-This copies a script to download the reports, which you should execute in the `test/junit` directory.
+This copies a script to download the reports, which you should execute in the root of the Kibana repository.
Next, run the CLI in `--no-github-update` mode so that it doesn't actually communicate with Github and `--no-report-update` to prevent the script from mutating the reports on disk and instead log the updated report.
```sh
-node scripts/report_failed_tests.js --verbose --no-github-update --no-report-update
+node scripts/report_failed_tests.js --verbose --no-github-update --no-report-update target/downloaded_junit/**/*.xml
```
Unless you specify the `GITHUB_TOKEN` environment variable requests to read existing issues will use anonymous access which is limited to 60 requests per hour.
\ No newline at end of file
diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts
index 3bcea44cf73b6..8a951ac969199 100644
--- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts
+++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts
@@ -17,6 +17,8 @@
* under the License.
*/
+import Path from 'path';
+
import { REPO_ROOT, run, createFailError, createFlagError } from '@kbn/dev-utils';
import globby from 'globby';
@@ -28,6 +30,8 @@ import { readTestReport } from './test_report';
import { addMessagesToReport } from './add_messages_to_report';
import { getReportMessageIter } from './report_metadata';
+const DEFAULT_PATTERNS = [Path.resolve(REPO_ROOT, 'target/junit/**/*.xml')];
+
export function runFailedTestsReporterCli() {
run(
async ({ log, flags }) => {
@@ -67,11 +71,15 @@ export function runFailedTestsReporterCli() {
throw createFlagError('Missing --build-url or process.env.BUILD_URL');
}
- const reportPaths = await globby(['target/junit/**/*.xml'], {
- cwd: REPO_ROOT,
+ const patterns = flags._.length ? flags._ : DEFAULT_PATTERNS;
+ const reportPaths = await globby(patterns, {
absolute: true,
});
+ if (!reportPaths.length) {
+ throw createFailError(`Unable to find any junit reports with patterns [${patterns}]`);
+ }
+
const newlyCreatedIssues: Array<{
failure: TestFailure;
newIssue: GithubIssueMini;
diff --git a/packages/kbn-test/src/functional_test_runner/cli.ts b/packages/kbn-test/src/functional_test_runner/cli.ts
index 2a8e0c3d7de9a..d744be9467311 100644
--- a/packages/kbn-test/src/functional_test_runner/cli.ts
+++ b/packages/kbn-test/src/functional_test_runner/cli.ts
@@ -113,6 +113,9 @@ export function runFtrCli() {
}
},
{
+ log: {
+ defaultLevel: 'debug',
+ },
flags: {
string: [
'config',
@@ -126,7 +129,6 @@ export function runFtrCli() {
boolean: ['bail', 'invert', 'test-stats', 'updateBaselines', 'throttle', 'headless'],
default: {
config: 'test/functional/config.js',
- debug: true,
},
help: `
--config=path path to a config file
diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md
index 90b1bb4fd5320..5757b6dff8d3f 100644
--- a/src/core/MIGRATION.md
+++ b/src/core/MIGRATION.md
@@ -1309,7 +1309,7 @@ This table shows where these uiExports have moved to in the New Platform. In mos
| `hacks` | n/a | Just run the code in your plugin's `start` method. |
| `home` | [`plugins.home.featureCatalogue.register`](./src/plugins/home/public/feature_catalogue) | Must add `home` as a dependency in your kibana.json. |
| `indexManagement` | | Should be an API on the indexManagement plugin. |
-| `injectDefaultVars` | n/a | Plugins will only be able to "whitelist" config values for the frontend. See [#41990](https://github.com/elastic/kibana/issues/41990) |
+| `injectDefaultVars` | n/a | Plugins will only be able to allow config values for the frontend. See [#41990](https://github.com/elastic/kibana/issues/41990) |
| `inspectorViews` | | Should be an API on the data (?) plugin. |
| `interpreter` | | Should be an API on the interpreter plugin. |
| `links` | n/a | Not necessary, just register your app via `core.application.register` |
@@ -1389,7 +1389,7 @@ class MyPlugin {
}
```
-If your plugin also have a client-side part, you can also expose configuration properties to it using a whitelisting mechanism with the configuration `exposeToBrowser` property.
+If your plugin also have a client-side part, you can also expose configuration properties to it using the configuration `exposeToBrowser` allow-list property.
```typescript
// my_plugin/server/index.ts
diff --git a/src/core/public/application/scoped_history.mock.ts b/src/core/public/application/scoped_history.mock.ts
index 41c72306a99f9..3b954313700f2 100644
--- a/src/core/public/application/scoped_history.mock.ts
+++ b/src/core/public/application/scoped_history.mock.ts
@@ -20,16 +20,16 @@
import { Location } from 'history';
import { ScopedHistory } from './scoped_history';
-type ScopedHistoryMock = jest.Mocked>;
+export type ScopedHistoryMock = jest.Mocked;
+
const createMock = ({
pathname = '/',
search = '',
hash = '',
key,
state,
- ...overrides
-}: Partial = {}) => {
- const mock: ScopedHistoryMock = {
+}: Partial = {}) => {
+ const mock: jest.Mocked> = {
block: jest.fn(),
createHref: jest.fn(),
createSubHistory: jest.fn(),
@@ -39,7 +39,6 @@ const createMock = ({
listen: jest.fn(),
push: jest.fn(),
replace: jest.fn(),
- ...overrides,
action: 'PUSH',
length: 1,
location: {
@@ -51,7 +50,9 @@ const createMock = ({
},
};
- return mock;
+ // jest.Mocked still expects private methods and properties to be present, even
+ // if not part of the public contract.
+ return mock as ScopedHistoryMock;
};
export const scopedHistoryMock = {
diff --git a/src/core/public/notifications/toasts/error_toast.tsx b/src/core/public/notifications/toasts/error_toast.tsx
index 6b53719839b0f..df8214ce771af 100644
--- a/src/core/public/notifications/toasts/error_toast.tsx
+++ b/src/core/public/notifications/toasts/error_toast.tsx
@@ -31,8 +31,7 @@ import {
} from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-
-import { OverlayStart } from '../../overlays';
+import { OverlayStart } from 'kibana/public';
import { I18nStart } from '../../i18n';
interface ErrorToastProps {
@@ -43,6 +42,17 @@ interface ErrorToastProps {
i18nContext: () => I18nStart['Context'];
}
+interface RequestError extends Error {
+ body?: { attributes?: { error: { caused_by: { type: string; reason: string } } } };
+}
+
+const isRequestError = (e: Error | RequestError): e is RequestError => {
+ if ('body' in e) {
+ return e.body?.attributes?.error?.caused_by !== undefined;
+ }
+ return false;
+};
+
/**
* This should instead be replaced by the overlay service once it's available.
* This does not use React portals so that if the parent toast times out, this modal
@@ -56,6 +66,17 @@ function showErrorDialog({
i18nContext,
}: Pick) {
const I18nContext = i18nContext();
+ let text = '';
+
+ if (isRequestError(error)) {
+ text += `${error?.body?.attributes?.error?.caused_by.type}\n`;
+ text += `${error?.body?.attributes?.error?.caused_by.reason}\n\n`;
+ }
+
+ if (error.stack) {
+ text += error.stack;
+ }
+
const modal = openModal(
mount(
@@ -65,11 +86,11 @@ function showErrorDialog({
- {error.stack && (
+ {text && (
- {error.stack}
+ {text}
)}
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 303d005197588..c811209dfa80f 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -1282,7 +1282,7 @@ export interface SavedObjectsCreateOptions {
}
// @public (undocumented)
-export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions {
+export interface SavedObjectsFindOptions {
// (undocumented)
defaultSearchOperator?: 'AND' | 'OR';
fields?: string[];
@@ -1294,6 +1294,8 @@ export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions {
id: string;
};
// (undocumented)
+ namespaces?: string[];
+ // (undocumented)
page?: number;
// (undocumented)
perPage?: number;
diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts
index c4daaf5d7f307..209f489e29139 100644
--- a/src/core/public/saved_objects/saved_objects_client.ts
+++ b/src/core/public/saved_objects/saved_objects_client.ts
@@ -294,6 +294,7 @@ export class SavedObjectsClient {
sortField: 'sort_field',
type: 'type',
filter: 'filter',
+ namespaces: 'namespaces',
preference: 'preference',
};
diff --git a/src/core/server/elasticsearch/client/mocks.ts b/src/core/server/elasticsearch/client/mocks.ts
index 75644435a7f2a..34e83922d4d86 100644
--- a/src/core/server/elasticsearch/client/mocks.ts
+++ b/src/core/server/elasticsearch/client/mocks.ts
@@ -28,7 +28,7 @@ const createInternalClientMock = (): DeeplyMockedKeys => {
node: 'http://localhost',
}) as any;
- const blackListedProps = [
+ const omittedProps = [
'_events',
'_eventsCount',
'_maxListeners',
@@ -39,9 +39,9 @@ const createInternalClientMock = (): DeeplyMockedKeys => {
'helpers',
];
- const mockify = (obj: Record, blacklist: string[] = []) => {
+ const mockify = (obj: Record, omitted: string[] = []) => {
Object.keys(obj)
- .filter((key) => !blacklist.includes(key))
+ .filter((key) => !omitted.includes(key))
.forEach((key) => {
const propType = typeof obj[key];
if (propType === 'function') {
@@ -52,7 +52,7 @@ const createInternalClientMock = (): DeeplyMockedKeys => {
});
};
- mockify(client, blackListedProps);
+ mockify(client, omittedProps);
client.transport = {
request: jest.fn(),
diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts
index bbef0a105c089..7d37af833d4c1 100644
--- a/src/core/server/http/http_server.mocks.ts
+++ b/src/core/server/http/http_server.mocks.ts
@@ -33,7 +33,7 @@ import {
} from './router';
import { OnPreResponseToolkit } from './lifecycle/on_pre_response';
import { OnPostAuthToolkit } from './lifecycle/on_post_auth';
-import { OnPreAuthToolkit } from './lifecycle/on_pre_auth';
+import { OnPreRoutingToolkit } from './lifecycle/on_pre_routing';
interface RequestFixtureOptions
{
auth?: { isAuthenticated: boolean };
@@ -161,7 +161,7 @@ const createLifecycleResponseFactoryMock = (): jest.Mocked;
+type ToolkitMock = jest.Mocked;
const createToolkitMock = (): ToolkitMock => {
return {
diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts
index 72cb0b2821c5c..601eba835a54e 100644
--- a/src/core/server/http/http_server.test.ts
+++ b/src/core/server/http/http_server.test.ts
@@ -1089,6 +1089,16 @@ describe('setup contract', () => {
});
});
+ describe('#registerOnPreRouting', () => {
+ test('does not throw if called after stop', async () => {
+ const { registerOnPreRouting } = await server.setup(config);
+ await server.stop();
+ expect(() => {
+ registerOnPreRouting((req, res) => res.unauthorized());
+ }).not.toThrow();
+ });
+ });
+
describe('#registerOnPreAuth', () => {
test('does not throw if called after stop', async () => {
const { registerOnPreAuth } = await server.setup(config);
diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts
index 1abf5c0c133bb..9c16162d69334 100644
--- a/src/core/server/http/http_server.ts
+++ b/src/core/server/http/http_server.ts
@@ -24,8 +24,9 @@ import { Logger, LoggerFactory } from '../logging';
import { HttpConfig } from './http_config';
import { createServer, getListenerOptions, getServerOptions } from './http_tools';
import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth';
+import { adoptToHapiOnPreAuth, OnPreAuthHandler } from './lifecycle/on_pre_auth';
import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth';
-import { adoptToHapiOnPreAuthFormat, OnPreAuthHandler } from './lifecycle/on_pre_auth';
+import { adoptToHapiOnRequest, OnPreRoutingHandler } from './lifecycle/on_pre_routing';
import { adoptToHapiOnPreResponseFormat, OnPreResponseHandler } from './lifecycle/on_pre_response';
import { IRouter, RouteConfigOptions, KibanaRouteState, isSafeMethod } from './router';
import {
@@ -49,8 +50,9 @@ export interface HttpServerSetup {
basePath: HttpServiceSetup['basePath'];
csp: HttpServiceSetup['csp'];
createCookieSessionStorageFactory: HttpServiceSetup['createCookieSessionStorageFactory'];
- registerAuth: HttpServiceSetup['registerAuth'];
+ registerOnPreRouting: HttpServiceSetup['registerOnPreRouting'];
registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];
+ registerAuth: HttpServiceSetup['registerAuth'];
registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];
registerOnPreResponse: HttpServiceSetup['registerOnPreResponse'];
getAuthHeaders: GetAuthHeaders;
@@ -64,7 +66,11 @@ export interface HttpServerSetup {
/** @internal */
export type LifecycleRegistrar = Pick<
HttpServerSetup,
- 'registerAuth' | 'registerOnPreAuth' | 'registerOnPostAuth' | 'registerOnPreResponse'
+ | 'registerOnPreRouting'
+ | 'registerOnPreAuth'
+ | 'registerAuth'
+ | 'registerOnPostAuth'
+ | 'registerOnPreResponse'
>;
export class HttpServer {
@@ -113,12 +119,13 @@ export class HttpServer {
return {
registerRouter: this.registerRouter.bind(this),
registerStaticDir: this.registerStaticDir.bind(this),
+ registerOnPreRouting: this.registerOnPreRouting.bind(this),
registerOnPreAuth: this.registerOnPreAuth.bind(this),
+ registerAuth: this.registerAuth.bind(this),
registerOnPostAuth: this.registerOnPostAuth.bind(this),
registerOnPreResponse: this.registerOnPreResponse.bind(this),
createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) =>
this.createCookieSessionStorageFactory(cookieOptions, config.basePath),
- registerAuth: this.registerAuth.bind(this),
basePath: basePathService,
csp: config.csp,
auth: {
@@ -222,7 +229,7 @@ export class HttpServer {
return;
}
- this.registerOnPreAuth((request, response, toolkit) => {
+ this.registerOnPreRouting((request, response, toolkit) => {
const oldUrl = request.url.href!;
const newURL = basePathService.remove(oldUrl);
const shouldRedirect = newURL !== oldUrl;
@@ -263,6 +270,17 @@ export class HttpServer {
}
}
+ private registerOnPreAuth(fn: OnPreAuthHandler) {
+ if (this.server === undefined) {
+ throw new Error('Server is not created yet');
+ }
+ if (this.stopped) {
+ this.log.warn(`registerOnPreAuth called after stop`);
+ }
+
+ this.server.ext('onPreAuth', adoptToHapiOnPreAuth(fn, this.log));
+ }
+
private registerOnPostAuth(fn: OnPostAuthHandler) {
if (this.server === undefined) {
throw new Error('Server is not created yet');
@@ -274,15 +292,15 @@ export class HttpServer {
this.server.ext('onPostAuth', adoptToHapiOnPostAuthFormat(fn, this.log));
}
- private registerOnPreAuth(fn: OnPreAuthHandler) {
+ private registerOnPreRouting(fn: OnPreRoutingHandler) {
if (this.server === undefined) {
throw new Error('Server is not created yet');
}
if (this.stopped) {
- this.log.warn(`registerOnPreAuth called after stop`);
+ this.log.warn(`registerOnPreRouting called after stop`);
}
- this.server.ext('onRequest', adoptToHapiOnPreAuthFormat(fn, this.log));
+ this.server.ext('onRequest', adoptToHapiOnRequest(fn, this.log));
}
private registerOnPreResponse(fn: OnPreResponseHandler) {
diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts
index 5e7ee7b658eca..51f11b15f2e09 100644
--- a/src/core/server/http/http_service.mock.ts
+++ b/src/core/server/http/http_service.mock.ts
@@ -29,7 +29,7 @@ import {
} from './types';
import { HttpService } from './http_service';
import { AuthStatus } from './auth_state_storage';
-import { OnPreAuthToolkit } from './lifecycle/on_pre_auth';
+import { OnPreRoutingToolkit } from './lifecycle/on_pre_routing';
import { AuthToolkit } from './lifecycle/auth';
import { sessionStorageMock } from './cookie_session_storage.mocks';
import { OnPostAuthToolkit } from './lifecycle/on_post_auth';
@@ -87,6 +87,7 @@ const createInternalSetupContractMock = () => {
config: jest.fn().mockReturnValue(configMock.create()),
} as unknown) as jest.MockedClass,
createCookieSessionStorageFactory: jest.fn(),
+ registerOnPreRouting: jest.fn(),
registerOnPreAuth: jest.fn(),
registerAuth: jest.fn(),
registerOnPostAuth: jest.fn(),
@@ -117,7 +118,8 @@ const createSetupContractMock = () => {
const mock: HttpServiceSetupMock = {
createCookieSessionStorageFactory: internalMock.createCookieSessionStorageFactory,
- registerOnPreAuth: internalMock.registerOnPreAuth,
+ registerOnPreRouting: internalMock.registerOnPreRouting,
+ registerOnPreAuth: jest.fn(),
registerAuth: internalMock.registerAuth,
registerOnPostAuth: internalMock.registerOnPostAuth,
registerOnPreResponse: internalMock.registerOnPreResponse,
@@ -173,7 +175,7 @@ const createHttpServiceMock = () => {
return mocked;
};
-const createOnPreAuthToolkitMock = (): jest.Mocked => ({
+const createOnPreAuthToolkitMock = (): jest.Mocked => ({
next: jest.fn(),
rewriteUrl: jest.fn(),
});
diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts
index 65d633260a791..e91f7d9375842 100644
--- a/src/core/server/http/index.ts
+++ b/src/core/server/http/index.ts
@@ -64,7 +64,7 @@ export {
SafeRouteMethod,
} from './router';
export { BasePathProxyServer } from './base_path_proxy_server';
-export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth';
+export { OnPreRoutingHandler, OnPreRoutingToolkit } from './lifecycle/on_pre_routing';
export {
AuthenticationHandler,
AuthHeaders,
@@ -78,6 +78,7 @@ export {
AuthResultType,
} from './lifecycle/auth';
export { OnPostAuthHandler, OnPostAuthToolkit } from './lifecycle/on_post_auth';
+export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth';
export {
OnPreResponseHandler,
OnPreResponseToolkit,
diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts
index 0ee53a04d9f87..3c5f22500e5e0 100644
--- a/src/core/server/http/integration_tests/core_services.test.ts
+++ b/src/core/server/http/integration_tests/core_services.test.ts
@@ -337,7 +337,7 @@ describe('http service', () => {
it('basePath information for an incoming request is available in legacy server', async () => {
const reqBasePath = '/requests-specific-base-path';
const { http } = await root.setup();
- http.registerOnPreAuth((req, res, toolkit) => {
+ http.registerOnPreRouting((req, res, toolkit) => {
http.basePath.set(req, reqBasePath);
return toolkit.next();
});
diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts
index cbab14115ba6b..b9548bf7a8d70 100644
--- a/src/core/server/http/integration_tests/lifecycle.test.ts
+++ b/src/core/server/http/integration_tests/lifecycle.test.ts
@@ -57,20 +57,22 @@ interface StorageData {
expires: number;
}
-describe('OnPreAuth', () => {
+describe('OnPreRouting', () => {
it('supports registering a request interceptor', async () => {
- const { registerOnPreAuth, server: innerServer, createRouter } = await server.setup(setupDeps);
+ const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup(
+ setupDeps
+ );
const router = createRouter('/');
router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'ok' }));
const callingOrder: string[] = [];
- registerOnPreAuth((req, res, t) => {
+ registerOnPreRouting((req, res, t) => {
callingOrder.push('first');
return t.next();
});
- registerOnPreAuth((req, res, t) => {
+ registerOnPreRouting((req, res, t) => {
callingOrder.push('second');
return t.next();
});
@@ -82,7 +84,9 @@ describe('OnPreAuth', () => {
});
it('supports request forwarding to specified url', async () => {
- const { registerOnPreAuth, server: innerServer, createRouter } = await server.setup(setupDeps);
+ const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup(
+ setupDeps
+ );
const router = createRouter('/');
router.get({ path: '/initial', validate: false }, (context, req, res) =>
@@ -93,13 +97,13 @@ describe('OnPreAuth', () => {
);
let urlBeforeForwarding;
- registerOnPreAuth((req, res, t) => {
+ registerOnPreRouting((req, res, t) => {
urlBeforeForwarding = ensureRawRequest(req).raw.req.url;
return t.rewriteUrl('/redirectUrl');
});
let urlAfterForwarding;
- registerOnPreAuth((req, res, t) => {
+ registerOnPreRouting((req, res, t) => {
// used by legacy platform
urlAfterForwarding = ensureRawRequest(req).raw.req.url;
return t.next();
@@ -113,6 +117,152 @@ describe('OnPreAuth', () => {
expect(urlAfterForwarding).toBe('/redirectUrl');
});
+ it('supports redirection from the interceptor', async () => {
+ const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup(
+ setupDeps
+ );
+ const router = createRouter('/');
+
+ const redirectUrl = '/redirectUrl';
+ router.get({ path: '/initial', validate: false }, (context, req, res) => res.ok());
+
+ registerOnPreRouting((req, res, t) =>
+ res.redirected({
+ headers: {
+ location: redirectUrl,
+ },
+ })
+ );
+ await server.start();
+
+ const result = await supertest(innerServer.listener).get('/initial').expect(302);
+
+ expect(result.header.location).toBe(redirectUrl);
+ });
+
+ it('supports rejecting request and adjusting response headers', async () => {
+ const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup(
+ setupDeps
+ );
+ const router = createRouter('/');
+
+ router.get({ path: '/', validate: false }, (context, req, res) => res.ok());
+
+ registerOnPreRouting((req, res, t) =>
+ res.unauthorized({
+ headers: {
+ 'www-authenticate': 'challenge',
+ },
+ })
+ );
+ await server.start();
+
+ const result = await supertest(innerServer.listener).get('/').expect(401);
+
+ expect(result.header['www-authenticate']).toBe('challenge');
+ });
+
+ it('does not expose error details if interceptor throws', async () => {
+ const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup(
+ setupDeps
+ );
+ const router = createRouter('/');
+
+ router.get({ path: '/', validate: false }, (context, req, res) => res.ok());
+
+ registerOnPreRouting((req, res, t) => {
+ throw new Error('reason');
+ });
+ await server.start();
+
+ const result = await supertest(innerServer.listener).get('/').expect(500);
+
+ expect(result.body.message).toBe('An internal server error occurred.');
+ expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(`
+ Array [
+ Array [
+ [Error: reason],
+ ],
+ ]
+ `);
+ });
+
+ it('returns internal error if interceptor returns unexpected result', async () => {
+ const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup(
+ setupDeps
+ );
+ const router = createRouter('/');
+
+ router.get({ path: '/', validate: false }, (context, req, res) => res.ok());
+
+ registerOnPreRouting((req, res, t) => ({} as any));
+ await server.start();
+
+ const result = await supertest(innerServer.listener).get('/').expect(500);
+
+ expect(result.body.message).toBe('An internal server error occurred.');
+ expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(`
+ Array [
+ Array [
+ [Error: Unexpected result from OnPreRouting. Expected OnPreRoutingResult or KibanaResponse, but given: [object Object].],
+ ],
+ ]
+ `);
+ });
+
+ it(`doesn't share request object between interceptors`, async () => {
+ const { registerOnPreRouting, server: innerServer, createRouter } = await server.setup(
+ setupDeps
+ );
+ const router = createRouter('/');
+
+ registerOnPreRouting((req, res, t) => {
+ // don't complain customField is not defined on Request type
+ (req as any).customField = { value: 42 };
+ return t.next();
+ });
+ registerOnPreRouting((req, res, t) => {
+ // don't complain customField is not defined on Request type
+ if (typeof (req as any).customField !== 'undefined') {
+ throw new Error('Request object was mutated');
+ }
+ return t.next();
+ });
+ router.get({ path: '/', validate: false }, (context, req, res) =>
+ // don't complain customField is not defined on Request type
+ res.ok({ body: { customField: String((req as any).customField) } })
+ );
+
+ await server.start();
+
+ await supertest(innerServer.listener).get('/').expect(200, { customField: 'undefined' });
+ });
+});
+
+describe('OnPreAuth', () => {
+ it('supports registering a request interceptor', async () => {
+ const { registerOnPreAuth, server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
+
+ router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'ok' }));
+
+ const callingOrder: string[] = [];
+ registerOnPreAuth((req, res, t) => {
+ callingOrder.push('first');
+ return t.next();
+ });
+
+ registerOnPreAuth((req, res, t) => {
+ callingOrder.push('second');
+ return t.next();
+ });
+ await server.start();
+
+ await supertest(innerServer.listener).get('/').expect(200, 'ok');
+
+ expect(callingOrder).toEqual(['first', 'second']);
+ });
+
it('supports redirection from the interceptor', async () => {
const { registerOnPreAuth, server: innerServer, createRouter } = await server.setup(setupDeps);
const router = createRouter('/');
@@ -203,20 +353,20 @@ describe('OnPreAuth', () => {
const router = createRouter('/');
registerOnPreAuth((req, res, t) => {
- // don't complain customField is not defined on Request type
- (req as any).customField = { value: 42 };
+ // @ts-expect-error customField property is not defined on request object
+ req.customField = { value: 42 };
return t.next();
});
registerOnPreAuth((req, res, t) => {
- // don't complain customField is not defined on Request type
- if (typeof (req as any).customField !== 'undefined') {
+ // @ts-expect-error customField property is not defined on request object
+ if (typeof req.customField !== 'undefined') {
throw new Error('Request object was mutated');
}
return t.next();
});
router.get({ path: '/', validate: false }, (context, req, res) =>
- // don't complain customField is not defined on Request type
- res.ok({ body: { customField: String((req as any).customField) } })
+ // @ts-expect-error customField property is not defined on request object
+ res.ok({ body: { customField: String(req.customField) } })
);
await server.start();
@@ -664,7 +814,7 @@ describe('Auth', () => {
it.skip('is the only place with access to the authorization header', async () => {
const {
- registerOnPreAuth,
+ registerOnPreRouting,
registerAuth,
registerOnPostAuth,
server: innerServer,
@@ -672,9 +822,9 @@ describe('Auth', () => {
} = await server.setup(setupDeps);
const router = createRouter('/');
- let fromRegisterOnPreAuth;
- await registerOnPreAuth((req, res, toolkit) => {
- fromRegisterOnPreAuth = req.headers.authorization;
+ let fromregisterOnPreRouting;
+ await registerOnPreRouting((req, res, toolkit) => {
+ fromregisterOnPreRouting = req.headers.authorization;
return toolkit.next();
});
@@ -701,7 +851,7 @@ describe('Auth', () => {
const token = 'Basic: user:password';
await supertest(innerServer.listener).get('/').set('Authorization', token).expect(200);
- expect(fromRegisterOnPreAuth).toEqual({});
+ expect(fromregisterOnPreRouting).toEqual({});
expect(fromRegisterAuth).toEqual({ authorization: token });
expect(fromRegisterOnPostAuth).toEqual({});
expect(fromRouteHandler).toEqual({});
@@ -1137,3 +1287,135 @@ describe('OnPreResponse', () => {
expect(requestBody).toStrictEqual({});
});
});
+
+describe('run interceptors in the right order', () => {
+ it('with Auth registered', async () => {
+ const {
+ registerOnPreRouting,
+ registerOnPreAuth,
+ registerAuth,
+ registerOnPostAuth,
+ registerOnPreResponse,
+ server: innerServer,
+ createRouter,
+ } = await server.setup(setupDeps);
+
+ const router = createRouter('/');
+
+ const executionOrder: string[] = [];
+ registerOnPreRouting((req, res, t) => {
+ executionOrder.push('onPreRouting');
+ return t.next();
+ });
+ registerOnPreAuth((req, res, t) => {
+ executionOrder.push('onPreAuth');
+ return t.next();
+ });
+ registerAuth((req, res, t) => {
+ executionOrder.push('auth');
+ return t.authenticated({});
+ });
+ registerOnPostAuth((req, res, t) => {
+ executionOrder.push('onPostAuth');
+ return t.next();
+ });
+ registerOnPreResponse((req, res, t) => {
+ executionOrder.push('onPreResponse');
+ return t.next();
+ });
+
+ router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'ok' }));
+
+ await server.start();
+
+ await supertest(innerServer.listener).get('/').expect(200);
+ expect(executionOrder).toEqual([
+ 'onPreRouting',
+ 'onPreAuth',
+ 'auth',
+ 'onPostAuth',
+ 'onPreResponse',
+ ]);
+ });
+
+ it('with no Auth registered', async () => {
+ const {
+ registerOnPreRouting,
+ registerOnPreAuth,
+ registerOnPostAuth,
+ registerOnPreResponse,
+ server: innerServer,
+ createRouter,
+ } = await server.setup(setupDeps);
+
+ const router = createRouter('/');
+
+ const executionOrder: string[] = [];
+ registerOnPreRouting((req, res, t) => {
+ executionOrder.push('onPreRouting');
+ return t.next();
+ });
+ registerOnPreAuth((req, res, t) => {
+ executionOrder.push('onPreAuth');
+ return t.next();
+ });
+ registerOnPostAuth((req, res, t) => {
+ executionOrder.push('onPostAuth');
+ return t.next();
+ });
+ registerOnPreResponse((req, res, t) => {
+ executionOrder.push('onPreResponse');
+ return t.next();
+ });
+
+ router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'ok' }));
+
+ await server.start();
+
+ await supertest(innerServer.listener).get('/').expect(200);
+ expect(executionOrder).toEqual(['onPreRouting', 'onPreAuth', 'onPostAuth', 'onPreResponse']);
+ });
+
+ it('when a user failed auth', async () => {
+ const {
+ registerOnPreRouting,
+ registerOnPreAuth,
+ registerOnPostAuth,
+ registerAuth,
+ registerOnPreResponse,
+ server: innerServer,
+ createRouter,
+ } = await server.setup(setupDeps);
+
+ const router = createRouter('/');
+
+ const executionOrder: string[] = [];
+ registerOnPreRouting((req, res, t) => {
+ executionOrder.push('onPreRouting');
+ return t.next();
+ });
+ registerOnPreAuth((req, res, t) => {
+ executionOrder.push('onPreAuth');
+ return t.next();
+ });
+ registerAuth((req, res, t) => {
+ executionOrder.push('auth');
+ return res.forbidden();
+ });
+ registerOnPostAuth((req, res, t) => {
+ executionOrder.push('onPostAuth');
+ return t.next();
+ });
+ registerOnPreResponse((req, res, t) => {
+ executionOrder.push('onPreResponse');
+ return t.next();
+ });
+
+ router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'ok' }));
+
+ await server.start();
+
+ await supertest(innerServer.listener).get('/').expect(403);
+ expect(executionOrder).toEqual(['onPreRouting', 'onPreAuth', 'auth', 'onPreResponse']);
+ });
+});
diff --git a/src/core/server/http/lifecycle/on_pre_auth.ts b/src/core/server/http/lifecycle/on_pre_auth.ts
index dc2ae6922fb94..f76fe87fd14a3 100644
--- a/src/core/server/http/lifecycle/on_pre_auth.ts
+++ b/src/core/server/http/lifecycle/on_pre_auth.ts
@@ -29,33 +29,21 @@ import {
enum ResultType {
next = 'next',
- rewriteUrl = 'rewriteUrl',
}
interface Next {
type: ResultType.next;
}
-interface RewriteUrl {
- type: ResultType.rewriteUrl;
- url: string;
-}
-
-type OnPreAuthResult = Next | RewriteUrl;
+type OnPreAuthResult = Next;
const preAuthResult = {
next(): OnPreAuthResult {
return { type: ResultType.next };
},
- rewriteUrl(url: string): OnPreAuthResult {
- return { type: ResultType.rewriteUrl, url };
- },
isNext(result: OnPreAuthResult): result is Next {
return result && result.type === ResultType.next;
},
- isRewriteUrl(result: OnPreAuthResult): result is RewriteUrl {
- return result && result.type === ResultType.rewriteUrl;
- },
};
/**
@@ -65,13 +53,10 @@ const preAuthResult = {
export interface OnPreAuthToolkit {
/** To pass request to the next handler */
next: () => OnPreAuthResult;
- /** Rewrite requested resources url before is was authenticated and routed to a handler */
- rewriteUrl: (url: string) => OnPreAuthResult;
}
const toolkit: OnPreAuthToolkit = {
next: preAuthResult.next,
- rewriteUrl: preAuthResult.rewriteUrl,
};
/**
@@ -88,9 +73,9 @@ export type OnPreAuthHandler = (
* @public
* Adopt custom request interceptor to Hapi lifecycle system.
* @param fn - an extension point allowing to perform custom logic for
- * incoming HTTP requests.
+ * incoming HTTP requests before a user has been authenticated.
*/
-export function adoptToHapiOnPreAuthFormat(fn: OnPreAuthHandler, log: Logger) {
+export function adoptToHapiOnPreAuth(fn: OnPreAuthHandler, log: Logger) {
return async function interceptPreAuthRequest(
request: Request,
responseToolkit: HapiResponseToolkit
@@ -107,13 +92,6 @@ export function adoptToHapiOnPreAuthFormat(fn: OnPreAuthHandler, log: Logger) {
return responseToolkit.continue;
}
- if (preAuthResult.isRewriteUrl(result)) {
- const { url } = result;
- request.setUrl(url);
- // We should update raw request as well since it can be proxied to the old platform
- request.raw.req.url = url;
- return responseToolkit.continue;
- }
throw new Error(
`Unexpected result from OnPreAuth. Expected OnPreAuthResult or KibanaResponse, but given: ${result}.`
);
diff --git a/src/core/server/http/lifecycle/on_pre_response.ts b/src/core/server/http/lifecycle/on_pre_response.ts
index 9c8c6fba690d1..4d1b53313a51f 100644
--- a/src/core/server/http/lifecycle/on_pre_response.ts
+++ b/src/core/server/http/lifecycle/on_pre_response.ts
@@ -64,7 +64,7 @@ const preResponseResult = {
};
/**
- * A tool set defining an outcome of OnPreAuth interceptor for incoming request.
+ * A tool set defining an outcome of OnPreResponse interceptor for incoming request.
* @public
*/
export interface OnPreResponseToolkit {
@@ -77,7 +77,7 @@ const toolkit: OnPreResponseToolkit = {
};
/**
- * See {@link OnPreAuthToolkit}.
+ * See {@link OnPreRoutingToolkit}.
* @public
*/
export type OnPreResponseHandler = (
diff --git a/src/core/server/http/lifecycle/on_pre_routing.ts b/src/core/server/http/lifecycle/on_pre_routing.ts
new file mode 100644
index 0000000000000..e62eb54f2398f
--- /dev/null
+++ b/src/core/server/http/lifecycle/on_pre_routing.ts
@@ -0,0 +1,125 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from 'hapi';
+import { Logger } from '../../logging';
+import {
+ HapiResponseAdapter,
+ KibanaRequest,
+ KibanaResponse,
+ lifecycleResponseFactory,
+ LifecycleResponseFactory,
+} from '../router';
+
+enum ResultType {
+ next = 'next',
+ rewriteUrl = 'rewriteUrl',
+}
+
+interface Next {
+ type: ResultType.next;
+}
+
+interface RewriteUrl {
+ type: ResultType.rewriteUrl;
+ url: string;
+}
+
+type OnPreRoutingResult = Next | RewriteUrl;
+
+const preRoutingResult = {
+ next(): OnPreRoutingResult {
+ return { type: ResultType.next };
+ },
+ rewriteUrl(url: string): OnPreRoutingResult {
+ return { type: ResultType.rewriteUrl, url };
+ },
+ isNext(result: OnPreRoutingResult): result is Next {
+ return result && result.type === ResultType.next;
+ },
+ isRewriteUrl(result: OnPreRoutingResult): result is RewriteUrl {
+ return result && result.type === ResultType.rewriteUrl;
+ },
+};
+
+/**
+ * @public
+ * A tool set defining an outcome of OnPreRouting interceptor for incoming request.
+ */
+export interface OnPreRoutingToolkit {
+ /** To pass request to the next handler */
+ next: () => OnPreRoutingResult;
+ /** Rewrite requested resources url before is was authenticated and routed to a handler */
+ rewriteUrl: (url: string) => OnPreRoutingResult;
+}
+
+const toolkit: OnPreRoutingToolkit = {
+ next: preRoutingResult.next,
+ rewriteUrl: preRoutingResult.rewriteUrl,
+};
+
+/**
+ * See {@link OnPreRoutingToolkit}.
+ * @public
+ */
+export type OnPreRoutingHandler = (
+ request: KibanaRequest,
+ response: LifecycleResponseFactory,
+ toolkit: OnPreRoutingToolkit
+) => OnPreRoutingResult | KibanaResponse | Promise;
+
+/**
+ * @public
+ * Adopt custom request interceptor to Hapi lifecycle system.
+ * @param fn - an extension point allowing to perform custom logic for
+ * incoming HTTP requests.
+ */
+export function adoptToHapiOnRequest(fn: OnPreRoutingHandler, log: Logger) {
+ return async function interceptPreRoutingRequest(
+ request: Request,
+ responseToolkit: HapiResponseToolkit
+ ): Promise {
+ const hapiResponseAdapter = new HapiResponseAdapter(responseToolkit);
+
+ try {
+ const result = await fn(KibanaRequest.from(request), lifecycleResponseFactory, toolkit);
+ if (result instanceof KibanaResponse) {
+ return hapiResponseAdapter.handle(result);
+ }
+
+ if (preRoutingResult.isNext(result)) {
+ return responseToolkit.continue;
+ }
+
+ if (preRoutingResult.isRewriteUrl(result)) {
+ const { url } = result;
+ request.setUrl(url);
+ // We should update raw request as well since it can be proxied to the old platform
+ request.raw.req.url = url;
+ return responseToolkit.continue;
+ }
+ throw new Error(
+ `Unexpected result from OnPreRouting. Expected OnPreRoutingResult or KibanaResponse, but given: ${result}.`
+ );
+ } catch (error) {
+ log.error(error);
+ return hapiResponseAdapter.toInternalError();
+ }
+ };
+}
diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts
index 241af1a3020cb..3df098a1df00d 100644
--- a/src/core/server/http/types.ts
+++ b/src/core/server/http/types.ts
@@ -25,6 +25,7 @@ import { HttpServerSetup } from './http_server';
import { SessionStorageCookieOptions } from './cookie_session_storage';
import { SessionStorageFactory } from './session_storage';
import { AuthenticationHandler } from './lifecycle/auth';
+import { OnPreRoutingHandler } from './lifecycle/on_pre_routing';
import { OnPreAuthHandler } from './lifecycle/on_pre_auth';
import { OnPostAuthHandler } from './lifecycle/on_post_auth';
import { OnPreResponseHandler } from './lifecycle/on_pre_response';
@@ -145,15 +146,26 @@ export interface HttpServiceSetup {
) => Promise>;
/**
- * To define custom logic to perform for incoming requests.
+ * To define custom logic to perform for incoming requests before server performs a route lookup.
*
* @remarks
- * Runs the handler before Auth interceptor performs a check that user has access to requested resources, so it's the
- * only place when you can forward a request to another URL right on the server.
- * Can register any number of registerOnPostAuth, which are called in sequence
+ * It's the only place when you can forward a request to another URL right on the server.
+ * Can register any number of registerOnPreRouting, which are called in sequence
+ * (from the first registered to the last). See {@link OnPreRoutingHandler}.
+ *
+ * @param handler {@link OnPreRoutingHandler} - function to call.
+ */
+ registerOnPreRouting: (handler: OnPreRoutingHandler) => void;
+
+ /**
+ * To define custom logic to perform for incoming requests before
+ * the Auth interceptor performs a check that user has access to requested resources.
+ *
+ * @remarks
+ * Can register any number of registerOnPreAuth, which are called in sequence
* (from the first registered to the last). See {@link OnPreAuthHandler}.
*
- * @param handler {@link OnPreAuthHandler} - function to call.
+ * @param handler {@link OnPreRoutingHandler} - function to call.
*/
registerOnPreAuth: (handler: OnPreAuthHandler) => void;
@@ -170,13 +182,11 @@ export interface HttpServiceSetup {
registerAuth: (handler: AuthenticationHandler) => void;
/**
- * To define custom logic to perform for incoming requests.
+ * To define custom logic after Auth interceptor did make sure a user has access to the requested resource.
*
* @remarks
- * Runs the handler after Auth interceptor
- * did make sure a user has access to the requested resource.
* The auth state is available at stage via http.auth.get(..)
- * Can register any number of registerOnPreAuth, which are called in sequence
+ * Can register any number of registerOnPostAuth, which are called in sequence
* (from the first registered to the last). See {@link OnPostAuthHandler}.
*
* @param handler {@link OnPostAuthHandler} - function to call.
diff --git a/src/core/server/index.ts b/src/core/server/index.ts
index dcaa5f2367214..706ec88c6ebfd 100644
--- a/src/core/server/index.ts
+++ b/src/core/server/index.ts
@@ -148,6 +148,8 @@ export {
LegacyRequest,
OnPreAuthHandler,
OnPreAuthToolkit,
+ OnPreRoutingHandler,
+ OnPreRoutingToolkit,
OnPostAuthHandler,
OnPostAuthToolkit,
OnPreResponseHandler,
diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts
index 6b34a4eb58319..fada40e773f12 100644
--- a/src/core/server/legacy/legacy_service.ts
+++ b/src/core/server/legacy/legacy_service.ts
@@ -301,6 +301,7 @@ export class LegacyService implements CoreService {
),
createRouter: () => router,
resources: setupDeps.core.httpResources.createRegistrar(router),
+ registerOnPreRouting: setupDeps.core.http.registerOnPreRouting,
registerOnPreAuth: setupDeps.core.http.registerOnPreAuth,
registerAuth: setupDeps.core.http.registerAuth,
registerOnPostAuth: setupDeps.core.http.registerOnPostAuth,
diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts
index a6dd13a12b527..c17b8df8bb52c 100644
--- a/src/core/server/plugins/plugin_context.ts
+++ b/src/core/server/plugins/plugin_context.ts
@@ -157,6 +157,7 @@ export function createPluginSetupContext(
),
createRouter: () => router,
resources: deps.httpResources.createRegistrar(router),
+ registerOnPreRouting: deps.http.registerOnPreRouting,
registerOnPreAuth: deps.http.registerOnPreAuth,
registerAuth: deps.http.registerAuth,
registerOnPostAuth: deps.http.registerOnPostAuth,
diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts
index 5da2235828b5c..27c0a5205ae38 100644
--- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts
+++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts
@@ -107,7 +107,97 @@ describe('getSortedObjectsForExport()', () => {
"calls": Array [
Array [
Object {
- "namespace": undefined,
+ "namespaces": undefined,
+ "perPage": 500,
+ "search": undefined,
+ "type": Array [
+ "index-pattern",
+ "search",
+ ],
+ },
+ ],
+ ],
+ "results": Array [
+ Object {
+ "type": "return",
+ "value": Promise {},
+ },
+ ],
+ }
+ `);
+ });
+
+ test('omits the `namespaces` property from the export', async () => {
+ savedObjectsClient.find.mockResolvedValueOnce({
+ total: 2,
+ saved_objects: [
+ {
+ id: '2',
+ type: 'search',
+ attributes: {},
+ namespaces: ['foo', 'bar'],
+ score: 0,
+ references: [
+ {
+ name: 'name',
+ type: 'index-pattern',
+ id: '1',
+ },
+ ],
+ },
+ {
+ id: '1',
+ type: 'index-pattern',
+ attributes: {},
+ namespaces: ['foo', 'bar'],
+ score: 0,
+ references: [],
+ },
+ ],
+ per_page: 1,
+ page: 0,
+ });
+ const exportStream = await exportSavedObjectsToStream({
+ savedObjectsClient,
+ exportSizeLimit: 500,
+ types: ['index-pattern', 'search'],
+ });
+
+ const response = await readStreamToCompletion(exportStream);
+
+ expect(response).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "attributes": Object {},
+ "id": "1",
+ "references": Array [],
+ "type": "index-pattern",
+ },
+ Object {
+ "attributes": Object {},
+ "id": "2",
+ "references": Array [
+ Object {
+ "id": "1",
+ "name": "name",
+ "type": "index-pattern",
+ },
+ ],
+ "type": "search",
+ },
+ Object {
+ "exportedCount": 2,
+ "missingRefCount": 0,
+ "missingReferences": Array [],
+ },
+ ]
+ `);
+ expect(savedObjectsClient.find).toMatchInlineSnapshot(`
+ [MockFunction] {
+ "calls": Array [
+ Array [
+ Object {
+ "namespaces": undefined,
"perPage": 500,
"search": undefined,
"type": Array [
@@ -257,7 +347,7 @@ describe('getSortedObjectsForExport()', () => {
"calls": Array [
Array [
Object {
- "namespace": undefined,
+ "namespaces": undefined,
"perPage": 500,
"search": "foo",
"type": Array [
@@ -346,7 +436,9 @@ describe('getSortedObjectsForExport()', () => {
"calls": Array [
Array [
Object {
- "namespace": "foo",
+ "namespaces": Array [
+ "foo",
+ ],
"perPage": 500,
"search": undefined,
"type": Array [
diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts
index 6e985c25aeaef..6cfe6f1be5669 100644
--- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts
+++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts
@@ -109,7 +109,7 @@ async function fetchObjectsToExport({
type: types,
search,
perPage: exportSizeLimit,
- namespace,
+ namespaces: namespace ? [namespace] : undefined,
});
if (findResponse.total > exportSizeLimit) {
throw Boom.badRequest(`Can't export more than ${exportSizeLimit} objects`);
@@ -162,10 +162,15 @@ export async function exportSavedObjectsToStream({
exportedObjects = sortObjects(rootObjects);
}
+ // redact attributes that should not be exported
+ const redactedObjects = exportedObjects.map>(
+ ({ namespaces, ...object }) => object
+ );
+
const exportDetails: SavedObjectsExportResultDetails = {
exportedCount: exportedObjects.length,
missingRefCount: missingReferences.length,
missingReferences,
};
- return createListStream([...exportedObjects, ...(excludeExportDetails ? [] : [exportDetails])]);
+ return createListStream([...redactedObjects, ...(excludeExportDetails ? [] : [exportDetails])]);
}
diff --git a/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.ts b/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.ts
index 81ba1d8235561..a998dbee0259e 100644
--- a/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.ts
+++ b/src/core/server/saved_objects/mappings/lib/get_root_properties_objects.ts
@@ -39,14 +39,14 @@ import { getRootProperties } from './get_root_properties';
* @return {EsPropertyMappings}
*/
-const blacklist = ['migrationVersion', 'references'];
+const omittedRootProps = ['migrationVersion', 'references'];
export function getRootPropertiesObjects(mappings: IndexMapping) {
const rootProperties = getRootProperties(mappings);
return Object.entries(rootProperties).reduce((acc, [key, value]) => {
// we consider the existence of the properties or type of object to designate that this is an object datatype
if (
- !blacklist.includes(key) &&
+ !omittedRootProps.includes(key) &&
((value as SavedObjectsComplexFieldMapping).properties || value.type === 'object')
) {
acc[key] = value;
diff --git a/src/core/server/saved_objects/routes/find.ts b/src/core/server/saved_objects/routes/find.ts
index 5c1c2c9a9ab87..6313a95b1fefa 100644
--- a/src/core/server/saved_objects/routes/find.ts
+++ b/src/core/server/saved_objects/routes/find.ts
@@ -45,11 +45,18 @@ export const registerFindRoute = (router: IRouter) => {
),
fields: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])),
filter: schema.maybe(schema.string()),
+ namespaces: schema.maybe(
+ schema.oneOf([schema.string(), schema.arrayOf(schema.string())])
+ ),
}),
},
},
router.handleLegacyErrors(async (context, req, res) => {
const query = req.query;
+
+ const namespaces =
+ typeof req.query.namespaces === 'string' ? [req.query.namespaces] : req.query.namespaces;
+
const result = await context.core.savedObjects.client.find({
perPage: query.per_page,
page: query.page,
@@ -62,6 +69,7 @@ export const registerFindRoute = (router: IRouter) => {
hasReference: query.has_reference,
fields: typeof query.fields === 'string' ? [query.fields] : query.fields,
filter: query.filter,
+ namespaces,
});
return res.ok({ body: result });
diff --git a/src/core/server/saved_objects/routes/integration_tests/find.test.ts b/src/core/server/saved_objects/routes/integration_tests/find.test.ts
index 33e12dd4e517d..d5a7710f04b39 100644
--- a/src/core/server/saved_objects/routes/integration_tests/find.test.ts
+++ b/src/core/server/saved_objects/routes/integration_tests/find.test.ts
@@ -81,6 +81,7 @@ describe('GET /api/saved_objects/_find', () => {
attributes: {},
score: 1,
references: [],
+ namespaces: ['default'],
},
{
type: 'index-pattern',
@@ -91,6 +92,7 @@ describe('GET /api/saved_objects/_find', () => {
attributes: {},
score: 1,
references: [],
+ namespaces: ['default'],
},
],
};
@@ -241,4 +243,38 @@ describe('GET /api/saved_objects/_find', () => {
defaultSearchOperator: 'OR',
});
});
+
+ it('accepts the query parameter namespaces as a string', async () => {
+ await supertest(httpSetup.server.listener)
+ .get('/api/saved_objects/_find?type=index-pattern&namespaces=foo')
+ .expect(200);
+
+ expect(savedObjectsClient.find).toHaveBeenCalledTimes(1);
+
+ const options = savedObjectsClient.find.mock.calls[0][0];
+ expect(options).toEqual({
+ perPage: 20,
+ page: 1,
+ type: ['index-pattern'],
+ namespaces: ['foo'],
+ defaultSearchOperator: 'OR',
+ });
+ });
+
+ it('accepts the query parameter namespaces as an array', async () => {
+ await supertest(httpSetup.server.listener)
+ .get('/api/saved_objects/_find?type=index-pattern&namespaces=default&namespaces=foo')
+ .expect(200);
+
+ expect(savedObjectsClient.find).toHaveBeenCalledTimes(1);
+
+ const options = savedObjectsClient.find.mock.calls[0][0];
+ expect(options).toEqual({
+ perPage: 20,
+ page: 1,
+ type: ['index-pattern'],
+ namespaces: ['default', 'foo'],
+ defaultSearchOperator: 'OR',
+ });
+ });
});
diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js
index ea749235cbb41..d563edbe66c9b 100644
--- a/src/core/server/saved_objects/service/lib/repository.test.js
+++ b/src/core/server/saved_objects/service/lib/repository.test.js
@@ -494,6 +494,7 @@ describe('SavedObjectsRepository', () => {
...obj,
migrationVersion: { [obj.type]: '1.1.1' },
version: mockVersion,
+ namespaces: obj.namespaces ?? [obj.namespace ?? 'default'],
...mockTimestampFields,
});
@@ -826,9 +827,19 @@ describe('SavedObjectsRepository', () => {
// Assert that both raw docs from the ES response are deserialized
expect(serializer.rawToSavedObject).toHaveBeenNthCalledWith(1, {
...response.items[0].create,
+ _source: {
+ ...response.items[0].create._source,
+ namespaces: response.items[0].create._source.namespaces,
+ },
_id: expect.stringMatching(/^myspace:config:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/),
});
- expect(serializer.rawToSavedObject).toHaveBeenNthCalledWith(2, response.items[1].create);
+ expect(serializer.rawToSavedObject).toHaveBeenNthCalledWith(2, {
+ ...response.items[1].create,
+ _source: {
+ ...response.items[1].create._source,
+ namespaces: response.items[1].create._source.namespaces,
+ },
+ });
// Assert that ID's are deserialized to remove the type and namespace
expect(result.saved_objects[0].id).toEqual(
@@ -985,7 +996,7 @@ describe('SavedObjectsRepository', () => {
const expectSuccessResult = ({ type, id }, doc) => ({
type,
id,
- ...(doc._source.namespaces && { namespaces: doc._source.namespaces }),
+ namespaces: doc._source.namespaces ?? ['default'],
...(doc._source.updated_at && { updated_at: doc._source.updated_at }),
version: encodeHitVersion(doc),
attributes: doc._source[type],
@@ -1027,12 +1038,12 @@ describe('SavedObjectsRepository', () => {
});
});
- it(`includes namespaces property for multi-namespace documents`, async () => {
+ it(`includes namespaces property for single-namespace and multi-namespace documents`, async () => {
const obj = { type: MULTI_NAMESPACE_TYPE, id: 'three' };
const result = await bulkGetSuccess([obj1, obj]);
expect(result).toEqual({
saved_objects: [
- expect.not.objectContaining({ namespaces: expect.anything() }),
+ expect.objectContaining({ namespaces: ['default'] }),
expect.objectContaining({ namespaces: expect.any(Array) }),
],
});
@@ -1350,12 +1361,13 @@ describe('SavedObjectsRepository', () => {
});
describe('returns', () => {
- const expectSuccessResult = ({ type, id, attributes, references }) => ({
+ const expectSuccessResult = ({ type, id, attributes, references, namespaces }) => ({
type,
id,
attributes,
references,
version: mockVersion,
+ namespaces: namespaces ?? ['default'],
...mockTimestampFields,
});
@@ -1389,12 +1401,12 @@ describe('SavedObjectsRepository', () => {
});
});
- it(`includes namespaces property for multi-namespace documents`, async () => {
+ it(`includes namespaces property for single-namespace and multi-namespace documents`, async () => {
const obj = { type: MULTI_NAMESPACE_TYPE, id: 'three' };
const result = await bulkUpdateSuccess([obj1, obj]);
expect(result).toEqual({
saved_objects: [
- expect.not.objectContaining({ namespaces: expect.anything() }),
+ expect.objectContaining({ namespaces: expect.any(Array) }),
expect.objectContaining({ namespaces: expect.any(Array) }),
],
});
@@ -1651,6 +1663,7 @@ describe('SavedObjectsRepository', () => {
version: mockVersion,
attributes,
references,
+ namespaces: [namespace ?? 'default'],
migrationVersion: { [type]: '1.1.1' },
});
});
@@ -1907,7 +1920,7 @@ describe('SavedObjectsRepository', () => {
await deleteByNamespaceSuccess(namespace);
const allTypes = registry.getAllTypes().map((type) => type.name);
expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, registry, {
- namespace,
+ namespaces: [namespace],
type: allTypes.filter((type) => !registry.isNamespaceAgnostic(type)),
});
});
@@ -2134,6 +2147,7 @@ describe('SavedObjectsRepository', () => {
score: doc._score,
attributes: doc._source[doc._source.type],
references: [],
+ namespaces: doc._source.type === NAMESPACE_AGNOSTIC_TYPE ? undefined : ['default'],
});
});
});
@@ -2143,7 +2157,7 @@ describe('SavedObjectsRepository', () => {
callAdminCluster.mockReturnValue(namespacedSearchResults);
const count = namespacedSearchResults.hits.hits.length;
- const response = await savedObjectsRepository.find({ type, namespace });
+ const response = await savedObjectsRepository.find({ type, namespaces: [namespace] });
expect(response.total).toBe(count);
expect(response.saved_objects).toHaveLength(count);
@@ -2157,6 +2171,7 @@ describe('SavedObjectsRepository', () => {
score: doc._score,
attributes: doc._source[doc._source.type],
references: [],
+ namespaces: doc._source.type === NAMESPACE_AGNOSTIC_TYPE ? undefined : [namespace],
});
});
});
@@ -2176,7 +2191,7 @@ describe('SavedObjectsRepository', () => {
describe('search dsl', () => {
it(`passes mappings, registry, search, defaultSearchOperator, searchFields, type, sortField, sortOrder and hasReference to getSearchDsl`, async () => {
const relevantOpts = {
- namespace,
+ namespaces: [namespace],
search: 'foo*',
searchFields: ['foo'],
type: [type],
@@ -2374,6 +2389,7 @@ describe('SavedObjectsRepository', () => {
title: 'Testing',
},
references: [],
+ namespaces: ['default'],
});
});
@@ -2384,10 +2400,10 @@ describe('SavedObjectsRepository', () => {
});
});
- it(`doesn't include namespaces if type is not multi-namespace`, async () => {
+ it(`include namespaces if type is not multi-namespace`, async () => {
const result = await getSuccess(type, id);
- expect(result).not.toMatchObject({
- namespaces: expect.anything(),
+ expect(result).toMatchObject({
+ namespaces: ['default'],
});
});
});
@@ -2908,10 +2924,10 @@ describe('SavedObjectsRepository', () => {
_id: `${type}:${id}`,
...mockVersionProps,
result: 'updated',
- ...(registry.isMultiNamespace(type) && {
- // don't need the rest of the source for test purposes, just the namespaces attribute
- get: { _source: { namespaces: [options?.namespace ?? 'default'] } },
- }),
+ // don't need the rest of the source for test purposes, just the namespace and namespaces attributes
+ get: {
+ _source: { namespaces: [options?.namespace ?? 'default'], namespace: options?.namespace },
+ },
}); // this._writeToCluster('update', ...)
const result = await savedObjectsRepository.update(type, id, attributes, options);
expect(callAdminCluster).toHaveBeenCalledTimes(registry.isMultiNamespace(type) ? 2 : 1);
@@ -3011,15 +3027,15 @@ describe('SavedObjectsRepository', () => {
it(`includes _sourceIncludes when type is multi-namespace`, async () => {
await updateSuccess(MULTI_NAMESPACE_TYPE, id, attributes);
- expectClusterCallArgs({ _sourceIncludes: ['namespaces'] }, 2);
+ expectClusterCallArgs({ _sourceIncludes: ['namespace', 'namespaces'] }, 2);
});
- it(`doesn't include _sourceIncludes when type is not multi-namespace`, async () => {
+ it(`includes _sourceIncludes when type is not multi-namespace`, async () => {
await updateSuccess(type, id, attributes);
expect(callAdminCluster).toHaveBeenLastCalledWith(
expect.any(String),
- expect.not.objectContaining({
- _sourceIncludes: expect.anything(),
+ expect.objectContaining({
+ _sourceIncludes: ['namespace', 'namespaces'],
})
);
});
@@ -3093,6 +3109,7 @@ describe('SavedObjectsRepository', () => {
version: mockVersion,
attributes,
references,
+ namespaces: [namespace],
});
});
@@ -3103,10 +3120,10 @@ describe('SavedObjectsRepository', () => {
});
});
- it(`doesn't include namespaces if type is not multi-namespace`, async () => {
+ it(`includes namespaces if type is not multi-namespace`, async () => {
const result = await updateSuccess(type, id, attributes);
- expect(result).not.toMatchObject({
- namespaces: expect.anything(),
+ expect(result).toMatchObject({
+ namespaces: ['default'],
});
});
});
diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts
index 880b71e164b5b..7a5ac9204627c 100644
--- a/src/core/server/saved_objects/service/lib/repository.ts
+++ b/src/core/server/saved_objects/service/lib/repository.ts
@@ -423,7 +423,7 @@ export class SavedObjectsRepository {
// When method == 'index' the bulkResponse doesn't include the indexed
// _source so we return rawMigratedDoc but have to spread the latest
// _seq_no and _primary_term values from the rawResponse.
- return this._serializer.rawToSavedObject({
+ return this._rawToSavedObject({
...rawMigratedDoc,
...{ _seq_no: rawResponse._seq_no, _primary_term: rawResponse._primary_term },
});
@@ -554,7 +554,7 @@ export class SavedObjectsRepository {
},
conflicts: 'proceed',
...getSearchDsl(this._mappings, this._registry, {
- namespace,
+ namespaces: namespace ? [namespace] : undefined,
type: typesToUpdate,
}),
},
@@ -590,7 +590,7 @@ export class SavedObjectsRepository {
sortField,
sortOrder,
fields,
- namespace,
+ namespaces,
type,
filter,
preference,
@@ -651,7 +651,7 @@ export class SavedObjectsRepository {
type: allowedTypes,
sortField,
sortOrder,
- namespace,
+ namespaces,
hasReference,
kueryNode,
}),
@@ -768,10 +768,16 @@ export class SavedObjectsRepository {
}
const time = doc._source.updated_at;
+
+ let namespaces = [];
+ if (!this._registry.isNamespaceAgnostic(type)) {
+ namespaces = doc._source.namespaces ?? [getNamespaceString(doc._source.namespace)];
+ }
+
return {
id,
type,
- ...(doc._source.namespaces && { namespaces: doc._source.namespaces }),
+ namespaces,
...(time && { updated_at: time }),
version: encodeHitVersion(doc),
attributes: doc._source[type],
@@ -817,10 +823,15 @@ export class SavedObjectsRepository {
const { updated_at: updatedAt } = response._source;
+ let namespaces = [];
+ if (!this._registry.isNamespaceAgnostic(type)) {
+ namespaces = response._source.namespaces ?? [getNamespaceString(response._source.namespace)];
+ }
+
return {
id,
type,
- ...(response._source.namespaces && { namespaces: response._source.namespaces }),
+ namespaces,
...(updatedAt && { updated_at: updatedAt }),
version: encodeHitVersion(response),
attributes: response._source[type],
@@ -874,7 +885,7 @@ export class SavedObjectsRepository {
body: {
doc,
},
- ...(this._registry.isMultiNamespace(type) && { _sourceIncludes: ['namespaces'] }),
+ _sourceIncludes: ['namespace', 'namespaces'],
});
if (updateResponse.status === 404) {
@@ -882,14 +893,19 @@ export class SavedObjectsRepository {
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
}
+ let namespaces = [];
+ if (!this._registry.isNamespaceAgnostic(type)) {
+ namespaces = updateResponse.get._source.namespaces ?? [
+ getNamespaceString(updateResponse.get._source.namespace),
+ ];
+ }
+
return {
id,
type,
updated_at: time,
version: encodeHitVersion(updateResponse),
- ...(this._registry.isMultiNamespace(type) && {
- namespaces: updateResponse.get._source.namespaces,
- }),
+ namespaces,
references,
attributes,
};
@@ -1142,9 +1158,14 @@ export class SavedObjectsRepository {
},
};
}
- namespaces = actualResult._source.namespaces;
+ namespaces = actualResult._source.namespaces ?? [
+ getNamespaceString(actualResult._source.namespace),
+ ];
versionProperties = getExpectedVersionProperties(version, actualResult);
} else {
+ if (this._registry.isSingleNamespace(type)) {
+ namespaces = [getNamespaceString(namespace)];
+ }
versionProperties = getExpectedVersionProperties(version);
}
@@ -1340,12 +1361,12 @@ export class SavedObjectsRepository {
return new Date().toISOString();
}
- // The internal representation of the saved object that the serializer returns
- // includes the namespace, and we use this for migrating documents. However, we don't
- // want the namespace to be returned from the repository, as the repository scopes each
- // method transparently to the specified namespace.
private _rawToSavedObject(raw: SavedObjectsRawDoc): SavedObject {
const savedObject = this._serializer.rawToSavedObject(raw);
+ const { namespace, type } = savedObject;
+ if (this._registry.isSingleNamespace(type)) {
+ savedObject.namespaces = [getNamespaceString(namespace)];
+ }
return omit(savedObject, 'namespace') as SavedObject;
}
diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts
index a0ffa91f53671..f916638c5251b 100644
--- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts
+++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts
@@ -196,19 +196,29 @@ describe('#getQueryParams', () => {
});
});
- describe('`namespace` parameter', () => {
- const createTypeClause = (type: string, namespace?: string) => {
+ describe('`namespaces` parameter', () => {
+ const createTypeClause = (type: string, namespaces?: string[]) => {
if (registry.isMultiNamespace(type)) {
return {
bool: {
- must: expect.arrayContaining([{ term: { namespaces: namespace ?? 'default' } }]),
+ must: expect.arrayContaining([{ terms: { namespaces: namespaces ?? ['default'] } }]),
must_not: [{ exists: { field: 'namespace' } }],
},
};
- } else if (namespace && registry.isSingleNamespace(type)) {
+ } else if (registry.isSingleNamespace(type)) {
+ const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? [];
+ const should: any = [];
+ if (nonDefaultNamespaces.length > 0) {
+ should.push({ terms: { namespace: nonDefaultNamespaces } });
+ }
+ if (namespaces?.includes('default')) {
+ should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } });
+ }
return {
bool: {
- must: expect.arrayContaining([{ term: { namespace } }]),
+ must: [{ term: { type } }],
+ should: expect.arrayContaining(should),
+ minimum_should_match: 1,
must_not: [{ exists: { field: 'namespaces' } }],
},
};
@@ -229,23 +239,45 @@ describe('#getQueryParams', () => {
);
};
- const test = (namespace?: string) => {
+ const test = (namespaces?: string[]) => {
for (const typeOrTypes of ALL_TYPE_SUBSETS) {
- const result = getQueryParams({ mappings, registry, type: typeOrTypes, namespace });
+ const result = getQueryParams({ mappings, registry, type: typeOrTypes, namespaces });
const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
- expectResult(result, ...types.map((x) => createTypeClause(x, namespace)));
+ expectResult(result, ...types.map((x) => createTypeClause(x, namespaces)));
}
// also test with no specified type/s
- const result = getQueryParams({ mappings, registry, type: undefined, namespace });
- expectResult(result, ...ALL_TYPES.map((x) => createTypeClause(x, namespace)));
+ const result = getQueryParams({ mappings, registry, type: undefined, namespaces });
+ expectResult(result, ...ALL_TYPES.map((x) => createTypeClause(x, namespaces)));
};
- it('filters results with "namespace" field when `namespace` is not specified', () => {
+ it('normalizes and deduplicates provided namespaces', () => {
+ const result = getQueryParams({
+ mappings,
+ registry,
+ search: '*',
+ namespaces: ['foo', '*', 'foo', 'bar', 'default'],
+ });
+
+ expectResult(
+ result,
+ ...ALL_TYPES.map((x) => createTypeClause(x, ['foo', 'default', 'bar']))
+ );
+ });
+
+ it('filters results with "namespace" field when `namespaces` is not specified', () => {
test(undefined);
});
it('filters results for specified namespace for appropriate type/s', () => {
- test('foo-namespace');
+ test(['foo-namespace']);
+ });
+
+ it('filters results for specified namespaces for appropriate type/s', () => {
+ test(['foo-namespace', 'default']);
+ });
+
+ it('filters results for specified `default` namespace for appropriate type/s', () => {
+ test(['default']);
});
});
});
@@ -353,4 +385,18 @@ describe('#getQueryParams', () => {
});
});
});
+
+ describe('namespaces property', () => {
+ ALL_TYPES.forEach((type) => {
+ it(`throws for ${type} when namespaces is an empty array`, () => {
+ expect(() =>
+ getQueryParams({
+ mappings,
+ registry,
+ namespaces: [],
+ })
+ ).toThrowError('cannot specify empty namespaces array');
+ });
+ });
+ });
});
diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts
index 40485564176a6..164756f9796a5 100644
--- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts
+++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts
@@ -63,25 +63,42 @@ function getFieldsForTypes(types: string[], searchFields?: string[]) {
*/
function getClauseForType(
registry: ISavedObjectTypeRegistry,
- namespace: string | undefined,
+ namespaces: string[] = ['default'],
type: string
) {
+ if (namespaces.length === 0) {
+ throw new Error('cannot specify empty namespaces array');
+ }
if (registry.isMultiNamespace(type)) {
return {
bool: {
- must: [{ term: { type } }, { term: { namespaces: namespace ?? 'default' } }],
+ must: [{ term: { type } }, { terms: { namespaces } }],
must_not: [{ exists: { field: 'namespace' } }],
},
};
- } else if (namespace && registry.isSingleNamespace(type)) {
+ } else if (registry.isSingleNamespace(type)) {
+ const should: Array> = [];
+ const eligibleNamespaces = namespaces.filter((namespace) => namespace !== 'default');
+ if (eligibleNamespaces.length > 0) {
+ should.push({ terms: { namespace: eligibleNamespaces } });
+ }
+ if (namespaces.includes('default')) {
+ should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } });
+ }
+ if (should.length === 0) {
+ // This is indicitive of a bug, and not user error.
+ throw new Error('unhandled search condition: expected at least 1 `should` clause.');
+ }
return {
bool: {
- must: [{ term: { type } }, { term: { namespace } }],
+ must: [{ term: { type } }],
+ should,
+ minimum_should_match: 1,
must_not: [{ exists: { field: 'namespaces' } }],
},
};
}
- // isSingleNamespace in the default namespace, or isNamespaceAgnostic
+ // isNamespaceAgnostic
return {
bool: {
must: [{ term: { type } }],
@@ -98,7 +115,7 @@ interface HasReferenceQueryParams {
interface QueryParams {
mappings: IndexMapping;
registry: ISavedObjectTypeRegistry;
- namespace?: string;
+ namespaces?: string[];
type?: string | string[];
search?: string;
searchFields?: string[];
@@ -113,7 +130,7 @@ interface QueryParams {
export function getQueryParams({
mappings,
registry,
- namespace,
+ namespaces,
type,
search,
searchFields,
@@ -122,6 +139,22 @@ export function getQueryParams({
kueryNode,
}: QueryParams) {
const types = getTypes(mappings, type);
+
+ // A de-duplicated set of namespaces makes for a more effecient query.
+ //
+ // Additonally, we treat the `*` namespace as the `default` namespace.
+ // In the Default Distribution, the `*` is automatically expanded to include all available namespaces.
+ // However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*`
+ // to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`,
+ // since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place
+ // would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard.
+ // We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716
+ const normalizedNamespaces = namespaces
+ ? Array.from(
+ new Set(namespaces.map((namespace) => (namespace === '*' ? 'default' : namespace)))
+ )
+ : undefined;
+
const bool: any = {
filter: [
...(kueryNode != null ? [esKuery.toElasticsearchQuery(kueryNode)] : []),
@@ -152,7 +185,9 @@ export function getQueryParams({
},
]
: undefined,
- should: types.map((shouldType) => getClauseForType(registry, namespace, shouldType)),
+ should: types.map((shouldType) =>
+ getClauseForType(registry, normalizedNamespaces, shouldType)
+ ),
minimum_should_match: 1,
},
},
diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts
index 95b7ffd117ee9..08ad72397e4a2 100644
--- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts
+++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts
@@ -57,9 +57,9 @@ describe('getSearchDsl', () => {
});
describe('passes control', () => {
- it('passes (mappings, schema, namespace, type, search, searchFields, hasReference) to getQueryParams', () => {
+ it('passes (mappings, schema, namespaces, type, search, searchFields, hasReference) to getQueryParams', () => {
const opts = {
- namespace: 'foo-namespace',
+ namespaces: ['foo-namespace'],
type: 'foo',
search: 'bar',
searchFields: ['baz'],
@@ -75,7 +75,7 @@ describe('getSearchDsl', () => {
expect(getQueryParams).toHaveBeenCalledWith({
mappings,
registry,
- namespace: opts.namespace,
+ namespaces: opts.namespaces,
type: opts.type,
search: opts.search,
searchFields: opts.searchFields,
diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts
index 74c25491aff8b..6de868c320240 100644
--- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts
+++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts
@@ -33,7 +33,7 @@ interface GetSearchDslOptions {
searchFields?: string[];
sortField?: string;
sortOrder?: string;
- namespace?: string;
+ namespaces?: string[];
hasReference?: {
type: string;
id: string;
@@ -53,7 +53,7 @@ export function getSearchDsl(
searchFields,
sortField,
sortOrder,
- namespace,
+ namespaces,
hasReference,
kueryNode,
} = options;
@@ -70,7 +70,7 @@ export function getSearchDsl(
...getQueryParams({
mappings,
registry,
- namespace,
+ namespaces,
type,
search,
searchFields,
diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts
index 2183b47b732f9..f9301d6598b1d 100644
--- a/src/core/server/saved_objects/types.ts
+++ b/src/core/server/saved_objects/types.ts
@@ -63,7 +63,7 @@ export interface SavedObjectStatusMeta {
*
* @public
*/
-export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions {
+export interface SavedObjectsFindOptions {
type: string | string[];
page?: number;
perPage?: number;
@@ -82,6 +82,7 @@ export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions {
hasReference?: { type: string; id: string };
defaultSearchOperator?: 'AND' | 'OR';
filter?: string;
+ namespaces?: string[];
/** An optional ES preference value to be used for the query **/
preference?: string;
}
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 3d3e1905577d9..a0e16602ba4bf 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -811,6 +811,7 @@ export interface HttpServiceSetup {
registerOnPostAuth: (handler: OnPostAuthHandler) => void;
registerOnPreAuth: (handler: OnPreAuthHandler) => void;
registerOnPreResponse: (handler: OnPreResponseHandler) => void;
+ registerOnPreRouting: (handler: OnPreRoutingHandler) => void;
registerRouteHandlerContext: (contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer;
}
@@ -1536,7 +1537,6 @@ export type OnPreAuthHandler = (request: KibanaRequest, response: LifecycleRespo
// @public
export interface OnPreAuthToolkit {
next: () => OnPreAuthResult;
- rewriteUrl: (url: string) => OnPreAuthResult;
}
// @public
@@ -1560,6 +1560,17 @@ export interface OnPreResponseToolkit {
next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult;
}
+// Warning: (ae-forgotten-export) The symbol "OnPreRoutingResult" needs to be exported by the entry point index.d.ts
+//
+// @public
+export type OnPreRoutingHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPreRoutingToolkit) => OnPreRoutingResult | KibanaResponse | Promise;
+
+// @public
+export interface OnPreRoutingToolkit {
+ next: () => OnPreRoutingResult;
+ rewriteUrl: (url: string) => OnPreRoutingResult;
+}
+
// @public
export interface OpsMetrics {
concurrent_connections: OpsServerMetrics['concurrent_connections'];
@@ -2164,7 +2175,7 @@ export interface SavedObjectsExportResultDetails {
export type SavedObjectsFieldMapping = SavedObjectsCoreFieldMapping | SavedObjectsComplexFieldMapping;
// @public (undocumented)
-export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions {
+export interface SavedObjectsFindOptions {
// (undocumented)
defaultSearchOperator?: 'AND' | 'OR';
fields?: string[];
@@ -2176,6 +2187,8 @@ export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions {
id: string;
};
// (undocumented)
+ namespaces?: string[];
+ // (undocumented)
page?: number;
// (undocumented)
perPage?: number;
@@ -2387,7 +2400,7 @@ export class SavedObjectsRepository {
deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise;
deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<{}>;
// (undocumented)
- find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, preference, }: SavedObjectsFindOptions): Promise>;
+ find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, }: SavedObjectsFindOptions): Promise>;
get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>;
incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{
id: string;
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_graph.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_graph.hjson
deleted file mode 100644
index db19c937ca990..0000000000000
--- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_graph.hjson
+++ /dev/null
@@ -1,76 +0,0 @@
-{
- // Adapted from Vega's https://vega.github.io/vega/examples/stacked-area-chart/
-
- $schema: https://vega.github.io/schema/vega/v5.json
- data: [
- {
- name: table
- values: [
- {x: 0, y: 28, c: 0}, {x: 0, y: 55, c: 1}, {x: 1, y: 43, c: 0}, {x: 1, y: 91, c: 1},
- {x: 2, y: 81, c: 0}, {x: 2, y: 53, c: 1}, {x: 3, y: 19, c: 0}, {x: 3, y: 87, c: 1},
- {x: 4, y: 52, c: 0}, {x: 4, y: 48, c: 1}, {x: 5, y: 24, c: 0}, {x: 5, y: 49, c: 1},
- {x: 6, y: 87, c: 0}, {x: 6, y: 66, c: 1}, {x: 7, y: 17, c: 0}, {x: 7, y: 27, c: 1},
- {x: 8, y: 68, c: 0}, {x: 8, y: 16, c: 1}, {x: 9, y: 49, c: 0}, {x: 9, y: 15, c: 1}
- ]
- transform: [
- {
- type: stack
- groupby: ["x"]
- sort: {field: "c"}
- field: y
- }
- ]
- }
- ]
- scales: [
- {
- name: x
- type: point
- range: width
- domain: {data: "table", field: "x"}
- }
- {
- name: y
- type: linear
- range: height
- nice: true
- zero: true
- domain: {data: "table", field: "y1"}
- }
- {
- name: color
- type: ordinal
- range: category
- domain: {data: "table", field: "c"}
- }
- ]
- marks: [
- {
- type: group
- from: {
- facet: {name: "series", data: "table", groupby: "c"}
- }
- marks: [
- {
- type: area
- from: {data: "series"}
- encode: {
- enter: {
- interpolate: {value: "monotone"}
- x: {scale: "x", field: "x"}
- y: {scale: "y", field: "y0"}
- y2: {scale: "y", field: "y1"}
- fill: {scale: "color", field: "c"}
- }
- update: {
- fillOpacity: {value: 1}
- }
- hover: {
- fillOpacity: {value: 0.5}
- }
- }
- }
- ]
- }
- ]
-}
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_image_512.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_image_512.png
deleted file mode 100644
index cc28886794f03..0000000000000
Binary files a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_image_512.png and /dev/null differ
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_image_256.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_image_256.png
deleted file mode 100644
index ac455ada3900b..0000000000000
Binary files a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_image_256.png and /dev/null differ
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_test.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_test.hjson
deleted file mode 100644
index 633b8658ad849..0000000000000
--- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_test.hjson
+++ /dev/null
@@ -1,20 +0,0 @@
-# This graph creates a single rectangle for the whole graph on top of a map
-# Note that the actual map tiles are not loaded
-{
- $schema: https://vega.github.io/schema/vega/v5.json
- config: {
- kibana: {type: "map", mapStyle: false}
- }
- marks: [
- {
- type: rect
- encode: {
- enter: {
- fill: {value: "#0f0"}
- width: {signal: "width"}
- height: {signal: "height"}
- }
- }
- }
- ]
-}
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_tooltip_test.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_tooltip_test.hjson
deleted file mode 100644
index 77465c8b3f007..0000000000000
--- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_tooltip_test.hjson
+++ /dev/null
@@ -1,44 +0,0 @@
-# This graph creates a single rectangle for the whole graph,
-# backed by a datum with two fields - fld1 & fld2
-# On mouse over, with 0 delay, it should show tooltip
-{
- config: {
- kibana: {
- tooltips: {
- // always center on the mark, not mouse x,y
- centerOnMark: false
- position: top
- padding: 20
- }
- }
- }
- data: [
- {
- name: table
- values: [
- {
- title: This is a long title
- fieldA: value of fld1
- fld2: 42
- }
- ]
- }
- ]
- $schema: https://vega.github.io/schema/vega/v5.json
- marks: [
- {
- from: {data: "table"}
- type: rect
- encode: {
- enter: {
- fill: {value: "#060"}
- x: {signal: "0"}
- y: {signal: "0"}
- width: {signal: "width"}
- height: {signal: "height"}
- tooltip: {signal: "datum || null"}
- }
- }
- }
- ]
-}
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js
deleted file mode 100644
index 30e7587707d2e..0000000000000
--- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import Bluebird from 'bluebird';
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import $ from 'jquery';
-
-import 'leaflet/dist/leaflet.js';
-import 'leaflet-vega';
-// Will be replaced with new path when tests are moved
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { createVegaVisualization } from '../../../../../../plugins/vis_type_vega/public/vega_visualization';
-import { ImageComparator } from 'test_utils/image_comparator';
-
-import vegaliteGraph from '!!raw-loader!./vegalite_graph.hjson';
-import vegaliteImage256 from './vegalite_image_256.png';
-import vegaliteImage512 from './vegalite_image_512.png';
-
-import vegaGraph from '!!raw-loader!./vega_graph.hjson';
-import vegaImage512 from './vega_image_512.png';
-
-import vegaTooltipGraph from '!!raw-loader!./vega_tooltip_test.hjson';
-
-import vegaMapGraph from '!!raw-loader!./vega_map_test.hjson';
-import vegaMapImage256 from './vega_map_image_256.png';
-// Will be replaced with new path when tests are moved
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { VegaParser } from '../../../../../../plugins/vis_type_vega/public/data_model/vega_parser';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { SearchAPI } from '../../../../../../plugins/vis_type_vega/public/data_model/search_api';
-
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { createVegaTypeDefinition } from '../../../../../../plugins/vis_type_vega/public/vega_type';
-// TODO This is an integration test and thus requires a running platform. When moving to the new platform,
-// this test has to be migrated to the newly created integration test environment.
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { npStart } from 'ui/new_platform';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { BaseVisType } from '../../../../../../plugins/visualizations/public/vis_types/base_vis_type';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { ExprVis } from '../../../../../../plugins/visualizations/public/expressions/vis';
-
-import {
- setInjectedVars,
- setData,
- setSavedObjects,
- setNotifications,
- setKibanaMapFactory,
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../../plugins/vis_type_vega/public/services';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { ServiceSettings } from '../../../../../../plugins/maps_legacy/public/map/service_settings';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { KibanaMap } from '../../../../../../plugins/maps_legacy/public/map/kibana_map';
-
-const THRESHOLD = 0.1;
-const PIXEL_DIFF = 30;
-
-describe('VegaVisualizations', () => {
- let domNode;
- let VegaVisualization;
- let vis;
- let imageComparator;
- let vegaVisualizationDependencies;
- let vegaVisType;
-
- setKibanaMapFactory((...args) => new KibanaMap(...args));
- setInjectedVars({
- emsTileLayerId: {},
- enableExternalUrls: true,
- esShardTimeout: 10000,
- });
- setData(npStart.plugins.data);
- setSavedObjects(npStart.core.savedObjects);
- setNotifications(npStart.core.notifications);
-
- const mockMapConfig = {
- includeElasticMapsService: true,
- proxyElasticMapsServiceInMaps: false,
- tilemap: {
- deprecated: {
- config: {
- options: {
- attribution: '',
- },
- },
- },
- options: {
- attribution: '',
- minZoom: 0,
- maxZoom: 10,
- },
- },
- regionmap: {
- includeElasticMapsService: true,
- layers: [],
- },
- manifestServiceUrl: '',
- emsFileApiUrl: 'https://vector.maps.elastic.co',
- emsTileApiUrl: 'https://tiles.maps.elastic.co',
- emsLandingPageUrl: 'https://maps.elastic.co/v7.7',
- emsFontLibraryUrl: 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf',
- emsTileLayerId: {
- bright: 'road_map',
- desaturated: 'road_map_desaturated',
- dark: 'dark_map',
- },
- };
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(
- ngMock.inject(() => {
- const serviceSettings = new ServiceSettings(mockMapConfig, mockMapConfig.tilemap);
- vegaVisualizationDependencies = {
- serviceSettings,
- core: {
- uiSettings: npStart.core.uiSettings,
- },
- plugins: {
- data: {
- query: {
- timefilter: {
- timefilter: {},
- },
- },
- },
- },
- };
-
- vegaVisType = new BaseVisType(createVegaTypeDefinition(vegaVisualizationDependencies));
- VegaVisualization = createVegaVisualization(vegaVisualizationDependencies);
- })
- );
-
- describe('VegaVisualization - basics', () => {
- beforeEach(async function () {
- setupDOM('512px', '512px');
- imageComparator = new ImageComparator();
-
- vis = new ExprVis({
- type: vegaVisType,
- });
- });
-
- afterEach(function () {
- teardownDOM();
- imageComparator.destroy();
- });
-
- it('should show vegalite graph and update on resize (may fail in dev env)', async function () {
- let vegaVis;
- try {
- vegaVis = new VegaVisualization(domNode, vis);
-
- const vegaParser = new VegaParser(
- vegaliteGraph,
- new SearchAPI({
- search: npStart.plugins.data.search,
- uiSettings: npStart.core.uiSettings,
- injectedMetadata: npStart.core.injectedMetadata,
- })
- );
- await vegaParser.parseAsync();
-
- await vegaVis.render(vegaParser, vis.params, { data: true });
- const mismatchedPixels1 = await compareImage(vegaliteImage512);
- expect(mismatchedPixels1).to.be.lessThan(PIXEL_DIFF);
-
- domNode.style.width = '256px';
- domNode.style.height = '256px';
-
- await vegaVis.render(vegaParser, vis.params, { resize: true });
- const mismatchedPixels2 = await compareImage(vegaliteImage256);
- expect(mismatchedPixels2).to.be.lessThan(PIXEL_DIFF);
- } finally {
- vegaVis.destroy();
- }
- });
-
- it('should show vega graph (may fail in dev env)', async function () {
- let vegaVis;
- try {
- vegaVis = new VegaVisualization(domNode, vis);
- const vegaParser = new VegaParser(
- vegaGraph,
- new SearchAPI({
- search: npStart.plugins.data.search,
- uiSettings: npStart.core.uiSettings,
- injectedMetadata: npStart.core.injectedMetadata,
- })
- );
- await vegaParser.parseAsync();
-
- await vegaVis.render(vegaParser, vis.params, { data: true });
- const mismatchedPixels = await compareImage(vegaImage512);
-
- expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF);
- } finally {
- vegaVis.destroy();
- }
- });
-
- it('should show vegatooltip on mouseover over a vega graph (may fail in dev env)', async () => {
- let vegaVis;
- try {
- vegaVis = new VegaVisualization(domNode, vis);
- const vegaParser = new VegaParser(
- vegaTooltipGraph,
- new SearchAPI({
- search: npStart.plugins.data.search,
- uiSettings: npStart.core.uiSettings,
- injectedMetadata: npStart.core.injectedMetadata,
- })
- );
- await vegaParser.parseAsync();
- await vegaVis.render(vegaParser, vis.params, { data: true });
-
- const $el = $(domNode);
- const offset = $el.offset();
-
- const event = new MouseEvent('mousemove', {
- view: window,
- bubbles: true,
- cancelable: true,
- clientX: offset.left + 10,
- clientY: offset.top + 10,
- });
-
- $el.find('canvas')[0].dispatchEvent(event);
-
- await Bluebird.delay(10);
-
- let tooltip = document.getElementById('vega-kibana-tooltip');
- expect(tooltip).to.be.ok();
- expect(tooltip.innerHTML).to.be(
- '
This is a long title
' +
- '
' +
- '
fieldA:
value of fld1
' +
- '
fld2:
42
' +
- '
'
- );
-
- vegaVis.destroy();
-
- tooltip = document.getElementById('vega-kibana-tooltip');
- expect(tooltip).to.not.be.ok();
- } finally {
- vegaVis.destroy();
- }
- });
-
- it('should show vega blank rectangle on top of a map (vegamap)', async () => {
- let vegaVis;
- try {
- vegaVis = new VegaVisualization(domNode, vis);
- const vegaParser = new VegaParser(
- vegaMapGraph,
- new SearchAPI({
- search: npStart.plugins.data.search,
- uiSettings: npStart.core.uiSettings,
- injectedMetadata: npStart.core.injectedMetadata,
- })
- );
- await vegaParser.parseAsync();
-
- domNode.style.width = '256px';
- domNode.style.height = '256px';
-
- await vegaVis.render(vegaParser, vis.params, { data: true });
- const mismatchedPixels = await compareImage(vegaMapImage256);
- expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF);
- } finally {
- vegaVis.destroy();
- }
- });
-
- it('should add a small subpixel value to the height of the canvas to avoid getting it set to 0', async () => {
- let vegaVis;
- try {
- vegaVis = new VegaVisualization(domNode, vis);
- const vegaParser = new VegaParser(
- `{
- "$schema": "https://vega.github.io/schema/vega/v5.json",
- "marks": [
- {
- "type": "text",
- "encode": {
- "update": {
- "text": {
- "value": "Test"
- },
- "align": {"value": "center"},
- "baseline": {"value": "middle"},
- "xc": {"signal": "width/2"},
- "yc": {"signal": "height/2"}
- fontSize: {value: "14"}
- }
- }
- }
- ]
- }`,
- new SearchAPI({
- search: npStart.plugins.data.search,
- uiSettings: npStart.core.uiSettings,
- injectedMetadata: npStart.core.injectedMetadata,
- })
- );
- await vegaParser.parseAsync();
-
- domNode.style.width = '256px';
- domNode.style.height = '256px';
-
- await vegaVis.render(vegaParser, vis.params, { data: true });
- const vegaView = vegaVis._vegaView._view;
- expect(vegaView.height()).to.be(250.00000001);
- } finally {
- vegaVis.destroy();
- }
- });
- });
-
- async function compareImage(expectedImageSource) {
- const elementList = domNode.querySelectorAll('canvas');
- expect(elementList.length).to.equal(1);
- const firstCanvasOnMap = elementList[0];
- return imageComparator.compareImage(firstCanvasOnMap, expectedImageSource, THRESHOLD);
- }
-
- function setupDOM(width, height) {
- domNode = document.createElement('div');
- domNode.style.top = '0';
- domNode.style.left = '0';
- domNode.style.width = width;
- domNode.style.height = height;
- domNode.style.position = 'fixed';
- domNode.style.border = '1px solid blue';
- domNode.style['pointer-events'] = 'none';
- document.body.appendChild(domNode);
- }
-
- function teardownDOM() {
- domNode.innerHTML = '';
- document.body.removeChild(domNode);
- }
-});
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_graph.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_graph.hjson
deleted file mode 100644
index 2132b0f77e6bc..0000000000000
--- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_graph.hjson
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- $schema: https://vega.github.io/schema/vega-lite/v4.json
- data: {
- format: {property: "aggregations.time_buckets.buckets"}
- values: {
- aggregations: {
- time_buckets: {
- buckets: [
- {key: 1512950400000, doc_count: 0}
- {key: 1513036800000, doc_count: 0}
- {key: 1513123200000, doc_count: 0}
- {key: 1513209600000, doc_count: 4545}
- {key: 1513296000000, doc_count: 4667}
- {key: 1513382400000, doc_count: 4660}
- {key: 1513468800000, doc_count: 133}
- {key: 1513555200000, doc_count: 0}
- {key: 1513641600000, doc_count: 0}
- {key: 1513728000000, doc_count: 0}
- ]
- }
- }
- status: 200
- }
- }
- mark: line
- encoding: {
- x: {
- field: key
- type: temporal
- axis: null
- }
- y: {
- field: doc_count
- type: quantitative
- axis: null
- }
- }
- config: {
- range: {
- category: {scheme: "elastic"}
- }
- mark: {color: "#54B399"}
- }
- autosize: {type: "fit", contains: "padding"}
-}
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_image_256.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_image_256.png
deleted file mode 100644
index 8f2d146287b08..0000000000000
Binary files a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_image_256.png and /dev/null differ
diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_image_512.png b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_image_512.png
deleted file mode 100644
index 82077a1096b99..0000000000000
Binary files a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_image_512.png and /dev/null differ
diff --git a/src/legacy/core_plugins/timelion/index.ts b/src/legacy/core_plugins/timelion/index.ts
deleted file mode 100644
index 9c8ab156d1a79..0000000000000
--- a/src/legacy/core_plugins/timelion/index.ts
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { resolve } from 'path';
-import { i18n } from '@kbn/i18n';
-import { Legacy } from 'kibana';
-import { LegacyPluginApi, LegacyPluginInitializer } from 'src/legacy/plugin_discovery/types';
-import { DEFAULT_APP_CATEGORIES } from '../../../core/server';
-
-const experimentalLabel = i18n.translate('timelion.uiSettings.experimentalLabel', {
- defaultMessage: 'experimental',
-});
-
-const timelionPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) =>
- new Plugin({
- require: ['kibana', 'elasticsearch'],
- config(Joi: any) {
- return Joi.object({
- enabled: Joi.boolean().default(true),
- ui: Joi.object({
- enabled: Joi.boolean().default(false),
- }).default(),
- graphiteUrls: Joi.array()
- .items(Joi.string().uri({ scheme: ['http', 'https'] }))
- .default([]),
- }).default();
- },
- // @ts-ignore
- // https://github.com/elastic/kibana/pull/44039#discussion_r326582255
- uiCapabilities() {
- return {
- timelion: {
- save: true,
- },
- };
- },
- publicDir: resolve(__dirname, 'public'),
- uiExports: {
- app: {
- title: 'Timelion',
- order: 8000,
- icon: 'plugins/timelion/icon.svg',
- euiIconType: 'timelionApp',
- main: 'plugins/timelion/app',
- category: DEFAULT_APP_CATEGORIES.kibana,
- },
- styleSheetPaths: resolve(__dirname, 'public/index.scss'),
- hacks: [resolve(__dirname, 'public/legacy')],
- uiSettingDefaults: {
- 'timelion:showTutorial': {
- name: i18n.translate('timelion.uiSettings.showTutorialLabel', {
- defaultMessage: 'Show tutorial',
- }),
- value: false,
- description: i18n.translate('timelion.uiSettings.showTutorialDescription', {
- defaultMessage: 'Should I show the tutorial by default when entering the timelion app?',
- }),
- category: ['timelion'],
- },
- 'timelion:es.timefield': {
- name: i18n.translate('timelion.uiSettings.timeFieldLabel', {
- defaultMessage: 'Time field',
- }),
- value: '@timestamp',
- description: i18n.translate('timelion.uiSettings.timeFieldDescription', {
- defaultMessage: 'Default field containing a timestamp when using {esParam}',
- values: { esParam: '.es()' },
- }),
- category: ['timelion'],
- },
- 'timelion:es.default_index': {
- name: i18n.translate('timelion.uiSettings.defaultIndexLabel', {
- defaultMessage: 'Default index',
- }),
- value: '_all',
- description: i18n.translate('timelion.uiSettings.defaultIndexDescription', {
- defaultMessage: 'Default elasticsearch index to search with {esParam}',
- values: { esParam: '.es()' },
- }),
- category: ['timelion'],
- },
- 'timelion:target_buckets': {
- name: i18n.translate('timelion.uiSettings.targetBucketsLabel', {
- defaultMessage: 'Target buckets',
- }),
- value: 200,
- description: i18n.translate('timelion.uiSettings.targetBucketsDescription', {
- defaultMessage: 'The number of buckets to shoot for when using auto intervals',
- }),
- category: ['timelion'],
- },
- 'timelion:max_buckets': {
- name: i18n.translate('timelion.uiSettings.maximumBucketsLabel', {
- defaultMessage: 'Maximum buckets',
- }),
- value: 2000,
- description: i18n.translate('timelion.uiSettings.maximumBucketsDescription', {
- defaultMessage: 'The maximum number of buckets a single datasource can return',
- }),
- category: ['timelion'],
- },
- 'timelion:default_columns': {
- name: i18n.translate('timelion.uiSettings.defaultColumnsLabel', {
- defaultMessage: 'Default columns',
- }),
- value: 2,
- description: i18n.translate('timelion.uiSettings.defaultColumnsDescription', {
- defaultMessage: 'Number of columns on a timelion sheet by default',
- }),
- category: ['timelion'],
- },
- 'timelion:default_rows': {
- name: i18n.translate('timelion.uiSettings.defaultRowsLabel', {
- defaultMessage: 'Default rows',
- }),
- value: 2,
- description: i18n.translate('timelion.uiSettings.defaultRowsDescription', {
- defaultMessage: 'Number of rows on a timelion sheet by default',
- }),
- category: ['timelion'],
- },
- 'timelion:min_interval': {
- name: i18n.translate('timelion.uiSettings.minimumIntervalLabel', {
- defaultMessage: 'Minimum interval',
- }),
- value: '1ms',
- description: i18n.translate('timelion.uiSettings.minimumIntervalDescription', {
- defaultMessage: 'The smallest interval that will be calculated when using "auto"',
- description:
- '"auto" is a technical value in that context, that should not be translated.',
- }),
- category: ['timelion'],
- },
- 'timelion:graphite.url': {
- name: i18n.translate('timelion.uiSettings.graphiteURLLabel', {
- defaultMessage: 'Graphite URL',
- description:
- 'The URL should be in the form of https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite',
- }),
- value: (server: Legacy.Server) => {
- const urls = server.config().get('timelion.graphiteUrls') as string[];
- if (urls.length === 0) {
- return null;
- } else {
- return urls[0];
- }
- },
- description: i18n.translate('timelion.uiSettings.graphiteURLDescription', {
- defaultMessage:
- '{experimentalLabel} The URL of your graphite host',
- values: { experimentalLabel: `[${experimentalLabel}]` },
- }),
- type: 'select',
- options: (server: Legacy.Server) => server.config().get('timelion.graphiteUrls'),
- category: ['timelion'],
- },
- 'timelion:quandl.key': {
- name: i18n.translate('timelion.uiSettings.quandlKeyLabel', {
- defaultMessage: 'Quandl key',
- }),
- value: 'someKeyHere',
- description: i18n.translate('timelion.uiSettings.quandlKeyDescription', {
- defaultMessage: '{experimentalLabel} Your API key from www.quandl.com',
- values: { experimentalLabel: `[${experimentalLabel}]` },
- }),
- category: ['timelion'],
- },
- },
- },
- });
-
-// eslint-disable-next-line import/no-default-export
-export default timelionPluginInitializer;
diff --git a/src/legacy/core_plugins/timelion/package.json b/src/legacy/core_plugins/timelion/package.json
deleted file mode 100644
index 8b138e3b76d1a..0000000000000
--- a/src/legacy/core_plugins/timelion/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "author": "Rashid Khan ",
- "name": "timelion",
- "version": "kibana"
-}
diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js
deleted file mode 100644
index 602b221b7d14d..0000000000000
--- a/src/legacy/core_plugins/timelion/public/app.js
+++ /dev/null
@@ -1,517 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-// required for `ngSanitize` angular module
-import 'angular-sanitize';
-
-import { i18n } from '@kbn/i18n';
-
-import routes from 'ui/routes';
-import { capabilities } from 'ui/capabilities';
-import { docTitle } from 'ui/doc_title';
-import { fatalError, toastNotifications } from 'ui/notify';
-import { timefilter } from 'ui/timefilter';
-import { npStart } from 'ui/new_platform';
-import { getSavedSheetBreadcrumbs, getCreateBreadcrumbs } from './breadcrumbs';
-import { getTimezone } from '../../../../plugins/vis_type_timelion/public';
-
-import 'uiExports/savedObjectTypes';
-
-require('ui/i18n');
-require('ui/autoload/all');
-
-// TODO: remove ui imports completely (move to plugins)
-import 'ui/directives/input_focus';
-import './directives/saved_object_finder';
-import 'ui/directives/listen';
-import './directives/saved_object_save_as_checkbox';
-import './services/saved_sheet_register';
-
-import rootTemplate from 'plugins/timelion/index.html';
-
-import { loadKbnTopNavDirectives } from '../../../../plugins/kibana_legacy/public';
-loadKbnTopNavDirectives(npStart.plugins.navigation.ui);
-
-require('plugins/timelion/directives/cells/cells');
-require('plugins/timelion/directives/fixed_element');
-require('plugins/timelion/directives/fullscreen/fullscreen');
-require('plugins/timelion/directives/timelion_expression_input');
-require('plugins/timelion/directives/timelion_help/timelion_help');
-require('plugins/timelion/directives/timelion_interval/timelion_interval');
-require('plugins/timelion/directives/timelion_save_sheet');
-require('plugins/timelion/directives/timelion_load_sheet');
-require('plugins/timelion/directives/timelion_options_sheet');
-
-document.title = 'Timelion - Kibana';
-
-const app = require('ui/modules').get('apps/timelion', ['i18n', 'ngSanitize']);
-
-routes.enable();
-
-routes.when('/:id?', {
- template: rootTemplate,
- reloadOnSearch: false,
- k7Breadcrumbs: ($injector, $route) =>
- $injector.invoke($route.current.params.id ? getSavedSheetBreadcrumbs : getCreateBreadcrumbs),
- badge: (uiCapabilities) => {
- if (uiCapabilities.timelion.save) {
- return undefined;
- }
-
- return {
- text: i18n.translate('timelion.badge.readOnly.text', {
- defaultMessage: 'Read only',
- }),
- tooltip: i18n.translate('timelion.badge.readOnly.tooltip', {
- defaultMessage: 'Unable to save Timelion sheets',
- }),
- iconType: 'glasses',
- };
- },
- resolve: {
- savedSheet: function (redirectWhenMissing, savedSheets, $route) {
- return savedSheets
- .get($route.current.params.id)
- .then((savedSheet) => {
- if ($route.current.params.id) {
- npStart.core.chrome.recentlyAccessed.add(
- savedSheet.getFullPath(),
- savedSheet.title,
- savedSheet.id
- );
- }
- return savedSheet;
- })
- .catch(
- redirectWhenMissing({
- search: '/',
- })
- );
- },
- },
-});
-
-const location = 'Timelion';
-
-app.controller('timelion', function (
- $http,
- $route,
- $routeParams,
- $scope,
- $timeout,
- AppState,
- config,
- kbnUrl
-) {
- // Keeping this at app scope allows us to keep the current page when the user
- // switches to say, the timepicker.
- $scope.page = config.get('timelion:showTutorial', true) ? 1 : 0;
- $scope.setPage = (page) => ($scope.page = page);
-
- timefilter.enableAutoRefreshSelector();
- timefilter.enableTimeRangeSelector();
-
- const savedVisualizations = npStart.plugins.visualizations.savedVisualizationsLoader;
- const timezone = getTimezone(config);
-
- const defaultExpression = '.es(*)';
- const savedSheet = $route.current.locals.savedSheet;
-
- $scope.topNavMenu = getTopNavMenu();
-
- $timeout(function () {
- if (config.get('timelion:showTutorial', true)) {
- $scope.toggleMenu('showHelp');
- }
- }, 0);
-
- $scope.transient = {};
- $scope.state = new AppState(getStateDefaults());
- function getStateDefaults() {
- return {
- sheet: savedSheet.timelion_sheet,
- selected: 0,
- columns: savedSheet.timelion_columns,
- rows: savedSheet.timelion_rows,
- interval: savedSheet.timelion_interval,
- };
- }
-
- function getTopNavMenu() {
- const newSheetAction = {
- id: 'new',
- label: i18n.translate('timelion.topNavMenu.newSheetButtonLabel', {
- defaultMessage: 'New',
- }),
- description: i18n.translate('timelion.topNavMenu.newSheetButtonAriaLabel', {
- defaultMessage: 'New Sheet',
- }),
- run: function () {
- kbnUrl.change('/');
- },
- testId: 'timelionNewButton',
- };
-
- const addSheetAction = {
- id: 'add',
- label: i18n.translate('timelion.topNavMenu.addChartButtonLabel', {
- defaultMessage: 'Add',
- }),
- description: i18n.translate('timelion.topNavMenu.addChartButtonAriaLabel', {
- defaultMessage: 'Add a chart',
- }),
- run: function () {
- $scope.$evalAsync(() => $scope.newCell());
- },
- testId: 'timelionAddChartButton',
- };
-
- const saveSheetAction = {
- id: 'save',
- label: i18n.translate('timelion.topNavMenu.saveSheetButtonLabel', {
- defaultMessage: 'Save',
- }),
- description: i18n.translate('timelion.topNavMenu.saveSheetButtonAriaLabel', {
- defaultMessage: 'Save Sheet',
- }),
- run: () => {
- $scope.$evalAsync(() => $scope.toggleMenu('showSave'));
- },
- testId: 'timelionSaveButton',
- };
-
- const deleteSheetAction = {
- id: 'delete',
- label: i18n.translate('timelion.topNavMenu.deleteSheetButtonLabel', {
- defaultMessage: 'Delete',
- }),
- description: i18n.translate('timelion.topNavMenu.deleteSheetButtonAriaLabel', {
- defaultMessage: 'Delete current sheet',
- }),
- disableButton: function () {
- return !savedSheet.id;
- },
- run: function () {
- const title = savedSheet.title;
- function doDelete() {
- savedSheet
- .delete()
- .then(() => {
- toastNotifications.addSuccess(
- i18n.translate('timelion.topNavMenu.delete.modal.successNotificationText', {
- defaultMessage: `Deleted '{title}'`,
- values: { title },
- })
- );
- kbnUrl.change('/');
- })
- .catch((error) => fatalError(error, location));
- }
-
- const confirmModalOptions = {
- confirmButtonText: i18n.translate('timelion.topNavMenu.delete.modal.confirmButtonLabel', {
- defaultMessage: 'Delete',
- }),
- title: i18n.translate('timelion.topNavMenu.delete.modalTitle', {
- defaultMessage: `Delete Timelion sheet '{title}'?`,
- values: { title },
- }),
- };
-
- $scope.$evalAsync(() => {
- npStart.core.overlays
- .openConfirm(
- i18n.translate('timelion.topNavMenu.delete.modal.warningText', {
- defaultMessage: `You can't recover deleted sheets.`,
- }),
- confirmModalOptions
- )
- .then((isConfirmed) => {
- if (isConfirmed) {
- doDelete();
- }
- });
- });
- },
- testId: 'timelionDeleteButton',
- };
-
- const openSheetAction = {
- id: 'open',
- label: i18n.translate('timelion.topNavMenu.openSheetButtonLabel', {
- defaultMessage: 'Open',
- }),
- description: i18n.translate('timelion.topNavMenu.openSheetButtonAriaLabel', {
- defaultMessage: 'Open Sheet',
- }),
- run: () => {
- $scope.$evalAsync(() => $scope.toggleMenu('showLoad'));
- },
- testId: 'timelionOpenButton',
- };
-
- const optionsAction = {
- id: 'options',
- label: i18n.translate('timelion.topNavMenu.optionsButtonLabel', {
- defaultMessage: 'Options',
- }),
- description: i18n.translate('timelion.topNavMenu.optionsButtonAriaLabel', {
- defaultMessage: 'Options',
- }),
- run: () => {
- $scope.$evalAsync(() => $scope.toggleMenu('showOptions'));
- },
- testId: 'timelionOptionsButton',
- };
-
- const helpAction = {
- id: 'help',
- label: i18n.translate('timelion.topNavMenu.helpButtonLabel', {
- defaultMessage: 'Help',
- }),
- description: i18n.translate('timelion.topNavMenu.helpButtonAriaLabel', {
- defaultMessage: 'Help',
- }),
- run: () => {
- $scope.$evalAsync(() => $scope.toggleMenu('showHelp'));
- },
- testId: 'timelionDocsButton',
- };
-
- if (capabilities.get().timelion.save) {
- return [
- newSheetAction,
- addSheetAction,
- saveSheetAction,
- deleteSheetAction,
- openSheetAction,
- optionsAction,
- helpAction,
- ];
- }
- return [newSheetAction, addSheetAction, openSheetAction, optionsAction, helpAction];
- }
-
- let refresher;
- const setRefreshData = function () {
- if (refresher) $timeout.cancel(refresher);
- const interval = timefilter.getRefreshInterval();
- if (interval.value > 0 && !interval.pause) {
- function startRefresh() {
- refresher = $timeout(function () {
- if (!$scope.running) $scope.search();
- startRefresh();
- }, interval.value);
- }
- startRefresh();
- }
- };
-
- const init = function () {
- $scope.running = false;
- $scope.search();
- setRefreshData();
-
- $scope.model = {
- timeRange: timefilter.getTime(),
- refreshInterval: timefilter.getRefreshInterval(),
- };
-
- $scope.$listen($scope.state, 'fetch_with_changes', $scope.search);
- timefilter.getFetch$().subscribe($scope.search);
-
- $scope.opts = {
- saveExpression: saveExpression,
- saveSheet: saveSheet,
- savedSheet: savedSheet,
- state: $scope.state,
- search: $scope.search,
- dontShowHelp: function () {
- config.set('timelion:showTutorial', false);
- $scope.setPage(0);
- $scope.closeMenus();
- },
- };
-
- $scope.menus = {
- showHelp: false,
- showSave: false,
- showLoad: false,
- showOptions: false,
- };
-
- $scope.toggleMenu = (menuName) => {
- const curState = $scope.menus[menuName];
- $scope.closeMenus();
- $scope.menus[menuName] = !curState;
- };
-
- $scope.closeMenus = () => {
- _.forOwn($scope.menus, function (value, key) {
- $scope.menus[key] = false;
- });
- };
- };
-
- $scope.onTimeUpdate = function ({ dateRange }) {
- $scope.model.timeRange = {
- ...dateRange,
- };
- timefilter.setTime(dateRange);
- };
-
- $scope.onRefreshChange = function ({ isPaused, refreshInterval }) {
- $scope.model.refreshInterval = {
- pause: isPaused,
- value: refreshInterval,
- };
- timefilter.setRefreshInterval({
- pause: isPaused,
- value: refreshInterval ? refreshInterval : $scope.refreshInterval.value,
- });
-
- setRefreshData();
- };
-
- $scope.$watch(
- function () {
- return savedSheet.lastSavedTitle;
- },
- function (newTitle) {
- docTitle.change(savedSheet.id ? newTitle : undefined);
- }
- );
-
- $scope.toggle = function (property) {
- $scope[property] = !$scope[property];
- };
-
- $scope.newSheet = function () {
- kbnUrl.change('/', {});
- };
-
- $scope.newCell = function () {
- $scope.state.sheet.push(defaultExpression);
- $scope.state.selected = $scope.state.sheet.length - 1;
- $scope.safeSearch();
- };
-
- $scope.setActiveCell = function (cell) {
- $scope.state.selected = cell;
- };
-
- $scope.search = function () {
- $scope.state.save();
- $scope.running = true;
-
- // parse the time range client side to make sure it behaves like other charts
- const timeRangeBounds = timefilter.getBounds();
-
- const httpResult = $http
- .post('../api/timelion/run', {
- sheet: $scope.state.sheet,
- time: _.assignIn(
- {
- from: timeRangeBounds.min,
- to: timeRangeBounds.max,
- },
- {
- interval: $scope.state.interval,
- timezone: timezone,
- }
- ),
- })
- .then((resp) => resp.data)
- .catch((resp) => {
- throw resp.data;
- });
-
- httpResult
- .then(function (resp) {
- $scope.stats = resp.stats;
- $scope.sheet = resp.sheet;
- _.each(resp.sheet, function (cell) {
- if (cell.exception) {
- $scope.state.selected = cell.plot;
- }
- });
- $scope.running = false;
- })
- .catch(function (resp) {
- $scope.sheet = [];
- $scope.running = false;
-
- const err = new Error(resp.message);
- err.stack = resp.stack;
- toastNotifications.addError(err, {
- title: i18n.translate('timelion.searchErrorTitle', {
- defaultMessage: 'Timelion request error',
- }),
- });
- });
- };
-
- $scope.safeSearch = _.debounce($scope.search, 500);
-
- function saveSheet() {
- savedSheet.timelion_sheet = $scope.state.sheet;
- savedSheet.timelion_interval = $scope.state.interval;
- savedSheet.timelion_columns = $scope.state.columns;
- savedSheet.timelion_rows = $scope.state.rows;
- savedSheet.save().then(function (id) {
- if (id) {
- toastNotifications.addSuccess({
- title: i18n.translate('timelion.saveSheet.successNotificationText', {
- defaultMessage: `Saved sheet '{title}'`,
- values: { title: savedSheet.title },
- }),
- 'data-test-subj': 'timelionSaveSuccessToast',
- });
-
- if (savedSheet.id !== $routeParams.id) {
- kbnUrl.change('/{{id}}', { id: savedSheet.id });
- }
- }
- });
- }
-
- function saveExpression(title) {
- savedVisualizations.get({ type: 'timelion' }).then(function (savedExpression) {
- savedExpression.visState.params = {
- expression: $scope.state.sheet[$scope.state.selected],
- interval: $scope.state.interval,
- };
- savedExpression.title = title;
- savedExpression.visState.title = title;
- savedExpression.save().then(function (id) {
- if (id) {
- toastNotifications.addSuccess(
- i18n.translate('timelion.saveExpression.successNotificationText', {
- defaultMessage: `Saved expression '{title}'`,
- values: { title: savedExpression.title },
- })
- );
- }
- });
- });
- }
-
- init();
-});
diff --git a/src/legacy/core_plugins/timelion/public/directives/cells/cells.js b/src/legacy/core_plugins/timelion/public/directives/cells/cells.js
deleted file mode 100644
index 104af3b1043d6..0000000000000
--- a/src/legacy/core_plugins/timelion/public/directives/cells/cells.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import { move } from 'ui/utils/collection';
-
-require('angular-sortable-view');
-require('plugins/timelion/directives/chart/chart');
-require('plugins/timelion/directives/timelion_grid');
-
-const app = require('ui/modules').get('apps/timelion', ['angular-sortable-view']);
-import html from './cells.html';
-
-app.directive('timelionCells', function () {
- return {
- restrict: 'E',
- scope: {
- sheet: '=',
- state: '=',
- transient: '=',
- onSearch: '=',
- onSelect: '=',
- },
- template: html,
- link: function ($scope) {
- $scope.removeCell = function (index) {
- _.pullAt($scope.state.sheet, index);
- $scope.onSearch();
- };
-
- $scope.dropCell = function (item, partFrom, partTo, indexFrom, indexTo) {
- $scope.onSelect(indexTo);
- move($scope.sheet, indexFrom, indexTo);
- };
- },
- };
-});
diff --git a/src/legacy/core_plugins/timelion/public/directives/fixed_element.js b/src/legacy/core_plugins/timelion/public/directives/fixed_element.js
deleted file mode 100644
index e3a8b2184bb20..0000000000000
--- a/src/legacy/core_plugins/timelion/public/directives/fixed_element.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import $ from 'jquery';
-
-const app = require('ui/modules').get('apps/timelion', []);
-app.directive('fixedElementRoot', function () {
- return {
- restrict: 'A',
- link: function ($elem) {
- let fixedAt;
- $(window).bind('scroll', function () {
- const fixed = $('[fixed-element]', $elem);
- const body = $('[fixed-element-body]', $elem);
- const top = fixed.offset().top;
-
- if ($(window).scrollTop() > top) {
- // This is a gross hack, but its better than it was. I guess
- fixedAt = $(window).scrollTop();
- fixed.addClass(fixed.attr('fixed-element'));
- body.addClass(fixed.attr('fixed-element-body'));
- body.css({ top: fixed.height() });
- }
-
- if ($(window).scrollTop() < fixedAt) {
- fixed.removeClass(fixed.attr('fixed-element'));
- body.removeClass(fixed.attr('fixed-element-body'));
- body.removeAttr('style');
- }
- });
- },
- };
-});
diff --git a/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js b/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js
deleted file mode 100644
index ae042310fd464..0000000000000
--- a/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import rison from 'rison-node';
-import { uiModules } from 'ui/modules';
-import 'ui/directives/input_focus';
-import savedObjectFinderTemplate from './saved_object_finder.html';
-import { savedSheetLoader } from '../services/saved_sheets';
-import { keyMap } from 'ui/directives/key_map';
-import {
- PaginateControlsDirectiveProvider,
- PaginateDirectiveProvider,
-} from '../../../../../plugins/kibana_legacy/public';
-import { PER_PAGE_SETTING } from '../../../../../plugins/saved_objects/common';
-import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../../plugins/visualizations/public';
-
-const module = uiModules.get('kibana');
-
-module
- .directive('paginate', PaginateDirectiveProvider)
- .directive('paginateControls', PaginateControlsDirectiveProvider)
- .directive('savedObjectFinder', function ($location, kbnUrl, Private, config) {
- return {
- restrict: 'E',
- scope: {
- type: '@',
- // optional make-url attr, sets the userMakeUrl in our scope
- userMakeUrl: '=?makeUrl',
- // optional on-choose attr, sets the userOnChoose in our scope
- userOnChoose: '=?onChoose',
- // optional useLocalManagement attr, removes link to management section
- useLocalManagement: '=?useLocalManagement',
- /**
- * @type {function} - an optional function. If supplied an `Add new X` button is shown
- * and this function is called when clicked.
- */
- onAddNew: '=',
- /**
- * @{type} boolean - set this to true, if you don't want the search box above the
- * table to automatically gain focus once loaded
- */
- disableAutoFocus: '=',
- },
- template: savedObjectFinderTemplate,
- controllerAs: 'finder',
- controller: function ($scope, $element) {
- const self = this;
-
- // the text input element
- const $input = $element.find('input[ng-model=filter]');
-
- // The number of items to show in the list
- $scope.perPage = config.get(PER_PAGE_SETTING);
-
- // the list that will hold the suggestions
- const $list = $element.find('ul');
-
- // the current filter string, used to check that returned results are still useful
- let currentFilter = $scope.filter;
-
- // the most recently entered search/filter
- let prevSearch;
-
- // the list of hits, used to render display
- self.hits = [];
-
- self.service = savedSheetLoader;
- self.properties = self.service.loaderProperties;
-
- filterResults();
-
- /**
- * Boolean that keeps track of whether hits are sorted ascending (true)
- * or descending (false) by title
- * @type {Boolean}
- */
- self.isAscending = true;
-
- /**
- * Sorts saved object finder hits either ascending or descending
- * @param {Array} hits Array of saved finder object hits
- * @return {Array} Array sorted either ascending or descending
- */
- self.sortHits = function (hits) {
- self.isAscending = !self.isAscending;
- self.hits = self.isAscending
- ? _.sortBy(hits, 'title')
- : _.sortBy(hits, 'title').reverse();
- };
-
- /**
- * Passed the hit objects and will determine if the
- * hit should have a url in the UI, returns it if so
- * @return {string|null} - the url or nothing
- */
- self.makeUrl = function (hit) {
- if ($scope.userMakeUrl) {
- return $scope.userMakeUrl(hit);
- }
-
- if (!$scope.userOnChoose) {
- return hit.url;
- }
-
- return '#';
- };
-
- self.preventClick = function ($event) {
- $event.preventDefault();
- };
-
- /**
- * Called when a hit object is clicked, can override the
- * url behavior if necessary.
- */
- self.onChoose = function (hit, $event) {
- if ($scope.userOnChoose) {
- $scope.userOnChoose(hit, $event);
- }
-
- const url = self.makeUrl(hit);
- if (!url || url === '#' || url.charAt(0) !== '#') return;
-
- $event.preventDefault();
-
- // we want the '/path', not '#/path'
- kbnUrl.change(url.substr(1));
- };
-
- $scope.$watch('filter', function (newFilter) {
- // ensure that the currentFilter changes from undefined to ''
- // which triggers
- currentFilter = newFilter || '';
- filterResults();
- });
-
- $scope.pageFirstItem = 0;
- $scope.pageLastItem = 0;
- $scope.onPageChanged = (page) => {
- $scope.pageFirstItem = page.firstItem;
- $scope.pageLastItem = page.lastItem;
- };
-
- //manages the state of the keyboard selector
- self.selector = {
- enabled: false,
- index: -1,
- };
-
- self.getLabel = function () {
- return _.words(self.properties.nouns).map(_.upperFirst).join(' ');
- };
-
- //key handler for the filter text box
- self.filterKeyDown = function ($event) {
- switch (keyMap[$event.keyCode]) {
- case 'enter':
- if (self.hitCount !== 1) return;
-
- const hit = self.hits[0];
- if (!hit) return;
-
- self.onChoose(hit, $event);
- $event.preventDefault();
- break;
- }
- };
-
- //key handler for the list items
- self.hitKeyDown = function ($event, page, paginate) {
- switch (keyMap[$event.keyCode]) {
- case 'tab':
- if (!self.selector.enabled) break;
-
- self.selector.index = -1;
- self.selector.enabled = false;
-
- //if the user types shift-tab return to the textbox
- //if the user types tab, set the focus to the currently selected hit.
- if ($event.shiftKey) {
- $input.focus();
- } else {
- $list.find('li.active a').focus();
- }
-
- $event.preventDefault();
- break;
- case 'down':
- if (!self.selector.enabled) break;
-
- if (self.selector.index + 1 < page.length) {
- self.selector.index += 1;
- }
- $event.preventDefault();
- break;
- case 'up':
- if (!self.selector.enabled) break;
-
- if (self.selector.index > 0) {
- self.selector.index -= 1;
- }
- $event.preventDefault();
- break;
- case 'right':
- if (!self.selector.enabled) break;
-
- if (page.number < page.count) {
- paginate.goToPage(page.number + 1);
- self.selector.index = 0;
- selectTopHit();
- }
- $event.preventDefault();
- break;
- case 'left':
- if (!self.selector.enabled) break;
-
- if (page.number > 1) {
- paginate.goToPage(page.number - 1);
- self.selector.index = 0;
- selectTopHit();
- }
- $event.preventDefault();
- break;
- case 'escape':
- if (!self.selector.enabled) break;
-
- $input.focus();
- $event.preventDefault();
- break;
- case 'enter':
- if (!self.selector.enabled) break;
-
- const hitIndex = (page.number - 1) * paginate.perPage + self.selector.index;
- const hit = self.hits[hitIndex];
- if (!hit) break;
-
- self.onChoose(hit, $event);
- $event.preventDefault();
- break;
- case 'shift':
- break;
- default:
- $input.focus();
- break;
- }
- };
-
- self.hitBlur = function () {
- self.selector.index = -1;
- self.selector.enabled = false;
- };
-
- self.manageObjects = function (type) {
- $location.url('/management/kibana/objects?_a=' + rison.encode({ tab: type }));
- };
-
- self.hitCountNoun = function () {
- return (self.hitCount === 1 ? self.properties.noun : self.properties.nouns).toLowerCase();
- };
-
- function selectTopHit() {
- setTimeout(function () {
- //triggering a focus event kicks off a new angular digest cycle.
- $list.find('a:first').focus();
- }, 0);
- }
-
- function filterResults() {
- if (!self.service) return;
- if (!self.properties) return;
-
- // track the filter that we use for this search,
- // but ensure that we don't search for the same
- // thing twice. This is called from multiple places
- // and needs to be smart about when it actually searches
- const filter = currentFilter;
- if (prevSearch === filter) return;
-
- prevSearch = filter;
-
- const isLabsEnabled = config.get(VISUALIZE_ENABLE_LABS_SETTING);
- self.service.find(filter).then(function (hits) {
- hits.hits = hits.hits.filter(
- (hit) => isLabsEnabled || _.get(hit, 'type.stage') !== 'experimental'
- );
- hits.total = hits.hits.length;
-
- // ensure that we don't display old results
- // as we can't really cancel requests
- if (currentFilter === filter) {
- self.hitCount = hits.total;
- self.hits = _.sortBy(hits.hits, 'title');
- }
- });
- }
- },
- };
- });
diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js
deleted file mode 100644
index 8b4c28a50b732..0000000000000
--- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * Timelion Expression Autocompleter
- *
- * This directive allows users to enter multiline timelion expressions. If the user has entered
- * a valid expression and then types a ".", this directive will display a list of suggestions.
- *
- * Users can navigate suggestions using the arrow keys. When a user selects a suggestion, it's
- * inserted into the expression and the caret position is updated to be inside of the newly-
- * added function's parentheses.
- *
- * Beneath the hood, we use a PEG grammar to validate the Timelion expression and detect if
- * the caret is in a position within the expression that allows functions to be suggested.
- *
- * NOTE: This directive doesn't work well with contenteditable divs. Challenges include:
- * - You have to replace markup with newline characters and spaces when passing the expression
- * to the grammar.
- * - You have to do the opposite when loading a saved expression, so that it appears correctly
- * within the contenteditable (i.e. replace newlines with markup).
- * - The Range and Selection APIs ignore newlines when providing caret position, so there is
- * literally no way to insert suggestions into the correct place in a multiline expression
- * that has more than a single consecutive newline.
- */
-
-import _ from 'lodash';
-import $ from 'jquery';
-import PEG from 'pegjs';
-import grammar from 'raw-loader!../../../../../plugins/vis_type_timelion/common/chain.peg';
-import timelionExpressionInputTemplate from './timelion_expression_input.html';
-import {
- SUGGESTION_TYPE,
- Suggestions,
- suggest,
- insertAtLocation,
-} from './timelion_expression_input_helpers';
-import { comboBoxKeys } from '@elastic/eui';
-import { npStart } from 'ui/new_platform';
-
-const Parser = PEG.generate(grammar);
-
-export function TimelionExpInput($http, $timeout) {
- return {
- restrict: 'E',
- scope: {
- rows: '=',
- sheet: '=',
- updateChart: '&',
- shouldPopoverSuggestions: '@',
- },
- replace: true,
- template: timelionExpressionInputTemplate,
- link: function (scope, elem) {
- const argValueSuggestions = npStart.plugins.visTypeTimelion.getArgValueSuggestions();
- const expressionInput = elem.find('[data-expression-input]');
- const functionReference = {};
- let suggestibleFunctionLocation = {};
-
- scope.suggestions = new Suggestions();
-
- function init() {
- $http.get('../api/timelion/functions').then(function (resp) {
- Object.assign(functionReference, {
- byName: _.keyBy(resp.data, 'name'),
- list: resp.data,
- });
- });
- }
-
- function setCaretOffset(caretOffset) {
- // Wait for Angular to update the input with the new expression and *then* we can set
- // the caret position.
- $timeout(() => {
- expressionInput.focus();
- expressionInput[0].selectionStart = expressionInput[0].selectionEnd = caretOffset;
- scope.$apply();
- }, 0);
- }
-
- function insertSuggestionIntoExpression(suggestionIndex) {
- if (scope.suggestions.isEmpty()) {
- return;
- }
-
- const { min, max } = suggestibleFunctionLocation;
- let insertedValue;
- let insertPositionMinOffset = 0;
-
- switch (scope.suggestions.type) {
- case SUGGESTION_TYPE.FUNCTIONS: {
- // Position the caret inside of the function parentheses.
- insertedValue = `${scope.suggestions.list[suggestionIndex].name}()`;
-
- // min advanced one to not replace function '.'
- insertPositionMinOffset = 1;
- break;
- }
- case SUGGESTION_TYPE.ARGUMENTS: {
- // Position the caret after the '='
- insertedValue = `${scope.suggestions.list[suggestionIndex].name}=`;
- break;
- }
- case SUGGESTION_TYPE.ARGUMENT_VALUE: {
- // Position the caret after the argument value
- insertedValue = `${scope.suggestions.list[suggestionIndex].name}`;
- break;
- }
- }
-
- const updatedExpression = insertAtLocation(
- insertedValue,
- scope.sheet,
- min + insertPositionMinOffset,
- max
- );
- scope.sheet = updatedExpression;
-
- const newCaretOffset = min + insertedValue.length;
- setCaretOffset(newCaretOffset);
- }
-
- function scrollToSuggestionAt(index) {
- // We don't cache these because the list changes based on user input.
- const suggestionsList = $('[data-suggestions-list]');
- const suggestionListItem = $('[data-suggestion-list-item]')[index];
- // Scroll to the position of the item relative to the list, not to the window.
- suggestionsList.scrollTop(suggestionListItem.offsetTop - suggestionsList[0].offsetTop);
- }
-
- function getCursorPosition() {
- if (expressionInput.length) {
- return expressionInput[0].selectionStart;
- }
- return null;
- }
-
- async function getSuggestions() {
- const suggestions = await suggest(
- scope.sheet,
- functionReference.list,
- Parser,
- getCursorPosition(),
- argValueSuggestions
- );
-
- // We're using ES6 Promises, not $q, so we have to wrap this in $apply.
- scope.$apply(() => {
- if (suggestions) {
- scope.suggestions.setList(suggestions.list, suggestions.type);
- scope.suggestions.show();
- suggestibleFunctionLocation = suggestions.location;
- $timeout(() => {
- const suggestionsList = $('[data-suggestions-list]');
- suggestionsList.scrollTop(0);
- }, 0);
- return;
- }
-
- suggestibleFunctionLocation = undefined;
- scope.suggestions.reset();
- });
- }
-
- function isNavigationalKey(key) {
- const keyCodes = _.values(comboBoxKeys);
- return keyCodes.includes(key);
- }
-
- scope.onFocusInput = () => {
- // Wait for the caret position of the input to update and then we can get suggestions
- // (which depends on the caret position).
- $timeout(getSuggestions, 0);
- };
-
- scope.onBlurInput = () => {
- scope.suggestions.hide();
- };
-
- scope.onKeyDownInput = (e) => {
- // If we've pressed any non-navigational keys, then the user has typed something and we
- // can exit early without doing any navigation. The keyup handler will pull up suggestions.
- if (!isNavigationalKey(e.key)) {
- return;
- }
-
- switch (e.keyCode) {
- case comboBoxKeys.ARROW_UP:
- if (scope.suggestions.isVisible) {
- // Up and down keys navigate through suggestions.
- e.preventDefault();
- scope.suggestions.stepForward();
- scrollToSuggestionAt(scope.suggestions.index);
- }
- break;
-
- case comboBoxKeys.ARROW_DOWN:
- if (scope.suggestions.isVisible) {
- // Up and down keys navigate through suggestions.
- e.preventDefault();
- scope.suggestions.stepBackward();
- scrollToSuggestionAt(scope.suggestions.index);
- }
- break;
-
- case comboBoxKeys.TAB:
- // If there are no suggestions or none is selected, the user tabs to the next input.
- if (scope.suggestions.isEmpty() || scope.suggestions.index < 0) {
- // Before letting the tab be handled to focus the next element
- // we need to hide the suggestions, otherwise it will focus these
- // instead of the time interval select.
- scope.suggestions.hide();
- return;
- }
-
- // If we have suggestions, complete the selected one.
- e.preventDefault();
- insertSuggestionIntoExpression(scope.suggestions.index);
- break;
-
- case comboBoxKeys.ENTER:
- if (e.metaKey || e.ctrlKey) {
- // Re-render the chart when the user hits CMD+ENTER.
- e.preventDefault();
- scope.updateChart();
- } else if (!scope.suggestions.isEmpty()) {
- // If the suggestions are open, complete the expression with the suggestion.
- e.preventDefault();
- insertSuggestionIntoExpression(scope.suggestions.index);
- }
- break;
-
- case comboBoxKeys.ESCAPE:
- e.preventDefault();
- scope.suggestions.hide();
- break;
- }
- };
-
- scope.onKeyUpInput = (e) => {
- // If the user isn't navigating, then we should update the suggestions based on their input.
- if (!isNavigationalKey(e.key)) {
- getSuggestions();
- }
- };
-
- scope.onClickExpression = () => {
- getSuggestions();
- };
-
- scope.onClickSuggestion = (index) => {
- insertSuggestionIntoExpression(index);
- };
-
- scope.getActiveSuggestionId = () => {
- if (scope.suggestions.isVisible && scope.suggestions.index > -1) {
- return `timelionSuggestion${scope.suggestions.index}`;
- }
- return '';
- };
-
- init();
- },
- };
-}
diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_grid.js b/src/legacy/core_plugins/timelion/public/directives/timelion_grid.js
deleted file mode 100644
index 256c35331d016..0000000000000
--- a/src/legacy/core_plugins/timelion/public/directives/timelion_grid.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import $ from 'jquery';
-
-const app = require('ui/modules').get('apps/timelion', []);
-app.directive('timelionGrid', function () {
- return {
- restrict: 'A',
- scope: {
- timelionGridRows: '=',
- timelionGridColumns: '=',
- },
- link: function ($scope, $elem) {
- function init() {
- setDimensions();
- }
-
- $scope.$on('$destroy', function () {
- $(window).off('resize'); //remove the handler added earlier
- });
-
- $(window).resize(function () {
- setDimensions();
- });
-
- $scope.$watchMulti(['timelionGridColumns', 'timelionGridRows'], function () {
- setDimensions();
- });
-
- function setDimensions() {
- const borderSize = 2;
- const headerSize = 45 + 35 + 28 + 20 * 2; // chrome + subnav + buttons + (container padding)
- const verticalPadding = 10;
-
- if ($scope.timelionGridColumns != null) {
- $elem.width($elem.parent().width() / $scope.timelionGridColumns - borderSize * 2);
- }
-
- if ($scope.timelionGridRows != null) {
- $elem.height(
- ($(window).height() - headerSize) / $scope.timelionGridRows -
- (verticalPadding + borderSize * 2)
- );
- }
- }
-
- init();
- },
- };
-});
diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_help/timelion_help.js b/src/legacy/core_plugins/timelion/public/directives/timelion_help/timelion_help.js
deleted file mode 100644
index 25f3df13153ba..0000000000000
--- a/src/legacy/core_plugins/timelion/public/directives/timelion_help/timelion_help.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import template from './timelion_help.html';
-import { i18n } from '@kbn/i18n';
-import { uiModules } from 'ui/modules';
-import _ from 'lodash';
-import moment from 'moment';
-import '../../components/timelionhelp_tabs_directive';
-
-const app = uiModules.get('apps/timelion', []);
-
-app.directive('timelionHelp', function ($http) {
- return {
- restrict: 'E',
- template,
- controller: function ($scope) {
- $scope.functions = {
- list: [],
- details: null,
- };
-
- $scope.activeTab = 'funcref';
- $scope.activateTab = function (tabName) {
- $scope.activeTab = tabName;
- };
-
- function init() {
- $scope.es = {
- invalidCount: 0,
- };
-
- $scope.translations = {
- nextButtonLabel: i18n.translate('timelion.help.nextPageButtonLabel', {
- defaultMessage: 'Next',
- }),
- previousButtonLabel: i18n.translate('timelion.help.previousPageButtonLabel', {
- defaultMessage: 'Previous',
- }),
- dontShowHelpButtonLabel: i18n.translate('timelion.help.dontShowHelpButtonLabel', {
- defaultMessage: `Don't show this again`,
- }),
- strongNextText: i18n.translate('timelion.help.welcome.content.strongNextText', {
- defaultMessage: 'Next',
- }),
- emphasizedEverythingText: i18n.translate(
- 'timelion.help.welcome.content.emphasizedEverythingText',
- {
- defaultMessage: 'everything',
- }
- ),
- notValidAdvancedSettingsPath: i18n.translate(
- 'timelion.help.configuration.notValid.advancedSettingsPathText',
- {
- defaultMessage: 'Management / Kibana / Advanced Settings',
- }
- ),
- validAdvancedSettingsPath: i18n.translate(
- 'timelion.help.configuration.valid.advancedSettingsPathText',
- {
- defaultMessage: 'Management/Kibana/Advanced Settings',
- }
- ),
- esAsteriskQueryDescription: i18n.translate(
- 'timelion.help.querying.esAsteriskQueryDescriptionText',
- {
- defaultMessage: 'hey Elasticsearch, find everything in my default index',
- }
- ),
- esIndexQueryDescription: i18n.translate(
- 'timelion.help.querying.esIndexQueryDescriptionText',
- {
- defaultMessage: 'use * as the q (query) for the logstash-* index',
- }
- ),
- strongAddText: i18n.translate('timelion.help.expressions.strongAddText', {
- defaultMessage: 'Add',
- }),
- twoExpressionsDescriptionTitle: i18n.translate(
- 'timelion.help.expressions.examples.twoExpressionsDescriptionTitle',
- {
- defaultMessage: 'Double the fun.',
- }
- ),
- customStylingDescriptionTitle: i18n.translate(
- 'timelion.help.expressions.examples.customStylingDescriptionTitle',
- {
- defaultMessage: 'Custom styling.',
- }
- ),
- namedArgumentsDescriptionTitle: i18n.translate(
- 'timelion.help.expressions.examples.namedArgumentsDescriptionTitle',
- {
- defaultMessage: 'Named arguments.',
- }
- ),
- groupedExpressionsDescriptionTitle: i18n.translate(
- 'timelion.help.expressions.examples.groupedExpressionsDescriptionTitle',
- {
- defaultMessage: 'Grouped expressions.',
- }
- ),
- };
-
- getFunctions();
- checkElasticsearch();
- }
-
- function getFunctions() {
- return $http.get('../api/timelion/functions').then(function (resp) {
- $scope.functions.list = resp.data;
- });
- }
- $scope.recheckElasticsearch = function () {
- $scope.es.valid = null;
- checkElasticsearch().then(function (valid) {
- if (!valid) $scope.es.invalidCount++;
- });
- };
-
- function checkElasticsearch() {
- return $http.get('../api/timelion/validate/es').then(function (resp) {
- if (resp.data.ok) {
- $scope.es.valid = true;
- $scope.es.stats = {
- min: moment(resp.data.min).format('LLL'),
- max: moment(resp.data.max).format('LLL'),
- field: resp.data.field,
- };
- } else {
- $scope.es.valid = false;
- $scope.es.invalidReason = (function () {
- try {
- const esResp = JSON.parse(resp.data.resp.response);
- return _.get(esResp, 'error.root_cause[0].reason');
- } catch (e) {
- if (_.get(resp, 'data.resp.message')) return _.get(resp, 'data.resp.message');
- if (_.get(resp, 'data.resp.output.payload.message'))
- return _.get(resp, 'data.resp.output.payload.message');
- return i18n.translate('timelion.help.unknownErrorMessage', {
- defaultMessage: 'Unknown error',
- });
- }
- })();
- }
- return $scope.es.valid;
- });
- }
- init();
- },
- };
-});
diff --git a/src/legacy/core_plugins/timelion/public/header.svg b/src/legacy/core_plugins/timelion/public/header.svg
deleted file mode 100644
index 56f2f0dc51a6e..0000000000000
--- a/src/legacy/core_plugins/timelion/public/header.svg
+++ /dev/null
@@ -1,227 +0,0 @@
-
-
diff --git a/src/legacy/core_plugins/timelion/public/icon.svg b/src/legacy/core_plugins/timelion/public/icon.svg
deleted file mode 100644
index ba9a704b3ade2..0000000000000
--- a/src/legacy/core_plugins/timelion/public/icon.svg
+++ /dev/null
@@ -1,97 +0,0 @@
-
-
-
-
diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts
deleted file mode 100644
index 7980291e2d462..0000000000000
--- a/src/legacy/core_plugins/timelion/public/legacy.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { PluginInitializerContext } from 'kibana/public';
-import { npSetup, npStart } from 'ui/new_platform';
-import { plugin } from '.';
-import { TimelionPluginSetupDependencies } from './plugin';
-import { LegacyDependenciesPlugin } from './shim';
-
-const setupPlugins: Readonly = {
- // Temporary solution
- // It will be removed when all dependent services are migrated to the new platform.
- __LEGACY: new LegacyDependenciesPlugin(),
-};
-
-const pluginInstance = plugin({} as PluginInitializerContext);
-
-export const setup = pluginInstance.setup(npSetup.core, setupPlugins);
-export const start = pluginInstance.start(npStart.core, npStart.plugins);
diff --git a/src/legacy/core_plugins/timelion/public/logo.png b/src/legacy/core_plugins/timelion/public/logo.png
deleted file mode 100644
index 7a62253697a06..0000000000000
Binary files a/src/legacy/core_plugins/timelion/public/logo.png and /dev/null differ
diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts
deleted file mode 100644
index 1f837303a2b3d..0000000000000
--- a/src/legacy/core_plugins/timelion/public/plugin.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import {
- CoreSetup,
- Plugin,
- PluginInitializerContext,
- IUiSettingsClient,
- CoreStart,
-} from 'kibana/public';
-import { getTimeChart } from './panels/timechart/timechart';
-import { Panel } from './panels/panel';
-import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim';
-import { KibanaLegacyStart } from '../../../../plugins/kibana_legacy/public';
-
-/** @internal */
-export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup {
- uiSettings: IUiSettingsClient;
- timelionPanels: Map;
-}
-
-/** @internal */
-export interface TimelionPluginSetupDependencies {
- // Temporary solution
- __LEGACY: LegacyDependenciesPlugin;
-}
-
-/** @internal */
-export class TimelionPlugin implements Plugin, void> {
- initializerContext: PluginInitializerContext;
-
- constructor(initializerContext: PluginInitializerContext) {
- this.initializerContext = initializerContext;
- }
-
- public async setup(core: CoreSetup, { __LEGACY }: TimelionPluginSetupDependencies) {
- const timelionPanels: Map = new Map();
-
- const dependencies: TimelionVisualizationDependencies = {
- uiSettings: core.uiSettings,
- timelionPanels,
- ...(await __LEGACY.setup(core, timelionPanels)),
- };
-
- this.registerPanels(dependencies);
- }
-
- private registerPanels(dependencies: TimelionVisualizationDependencies) {
- const timeChartPanel: Panel = getTimeChart(dependencies);
-
- dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel);
- }
-
- public start(core: CoreStart, { kibanaLegacy }: { kibanaLegacy: KibanaLegacyStart }) {
- kibanaLegacy.loadFontAwesome();
- }
-
- public stop(): void {}
-}
diff --git a/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts b/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts
deleted file mode 100644
index 1fb29de83d3d7..0000000000000
--- a/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { npStart } from 'ui/new_platform';
-// @ts-ignore
-import { uiModules } from 'ui/modules';
-import { SavedObjectLoader } from '../../../../../plugins/saved_objects/public';
-import { createSavedSheetClass } from './_saved_sheet';
-
-const module = uiModules.get('app/sheet');
-
-const savedObjectsClient = npStart.core.savedObjects.client;
-const services = {
- savedObjectsClient,
- indexPatterns: npStart.plugins.data.indexPatterns,
- search: npStart.plugins.data.search,
- chrome: npStart.core.chrome,
- overlays: npStart.core.overlays,
-};
-
-const SavedSheet = createSavedSheetClass(services, npStart.core.uiSettings);
-
-export const savedSheetLoader = new SavedObjectLoader(
- SavedSheet,
- savedObjectsClient,
- npStart.core.chrome
-);
-savedSheetLoader.urlFor = (id) => `#/${encodeURIComponent(id)}`;
-// Customize loader properties since adding an 's' on type doesn't work for type 'timelion-sheet'.
-savedSheetLoader.loaderProperties = {
- name: 'timelion-sheet',
- noun: 'Saved Sheets',
- nouns: 'saved sheets',
-};
-
-// This is the only thing that gets injected into controllers
-module.service('savedSheets', () => savedSheetLoader);
diff --git a/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts b/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts
deleted file mode 100644
index 8122259f1c991..0000000000000
--- a/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import 'ngreact';
-import 'brace/mode/hjson';
-import 'brace/ext/searchbox';
-import 'ui/accessibility/kbn_ui_ace_keyboard_mode';
-
-import { once } from 'lodash';
-// @ts-ignore
-import { uiModules } from 'ui/modules';
-import { Panel } from '../panels/panel';
-// @ts-ignore
-import { Chart } from '../directives/chart/chart';
-// @ts-ignore
-import { TimelionInterval } from '../directives/timelion_interval/timelion_interval';
-// @ts-ignore
-import { TimelionExpInput } from '../directives/timelion_expression_input';
-// @ts-ignore
-import { TimelionExpressionSuggestions } from '../directives/timelion_expression_suggestions/timelion_expression_suggestions';
-
-/** @internal */
-export const initTimelionLegacyModule = once((timelionPanels: Map): void => {
- require('ui/state_management/app_state');
-
- uiModules
- .get('apps/timelion', [])
- .controller('TimelionVisController', function ($scope: any) {
- $scope.$on('timelionChartRendered', (event: any) => {
- event.stopPropagation();
- $scope.renderComplete();
- });
- })
- .constant('timelionPanels', timelionPanels)
- .directive('chart', Chart)
- .directive('timelionInterval', TimelionInterval)
- .directive('timelionExpressionSuggestions', TimelionExpressionSuggestions)
- .directive('timelionExpressionInput', TimelionExpInput);
-});
diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js
index 53f5185442688..952c35df244c1 100644
--- a/src/legacy/server/config/schema.js
+++ b/src/legacy/server/config/schema.js
@@ -237,7 +237,7 @@ export default () =>
manifestServiceUrl: Joi.string().default('').allow(''),
emsFileApiUrl: Joi.string().default('https://vector.maps.elastic.co'),
emsTileApiUrl: Joi.string().default('https://tiles.maps.elastic.co'),
- emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v7.8'),
+ emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v7.9'),
emsFontLibraryUrl: Joi.string().default(
'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf'
),
diff --git a/src/legacy/ui/public/state_management/__tests__/state.js b/src/legacy/ui/public/state_management/__tests__/state.js
index cde123e6c1d85..b6c705e814509 100644
--- a/src/legacy/ui/public/state_management/__tests__/state.js
+++ b/src/legacy/ui/public/state_management/__tests__/state.js
@@ -21,6 +21,7 @@ import sinon from 'sinon';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { encode as encodeRison } from 'rison-node';
+import uiRoutes from 'ui/routes';
import '../../private';
import { toastNotifications } from '../../notify';
import * as FatalErrorNS from '../../notify/fatal_error';
@@ -38,6 +39,8 @@ describe('State Management', () => {
const sandbox = sinon.createSandbox();
afterEach(() => sandbox.restore());
+ uiRoutes.enable();
+
describe('Enabled', () => {
let $rootScope;
let $location;
diff --git a/src/plugins/console/public/application/components/editor_example.tsx b/src/plugins/console/public/application/components/editor_example.tsx
index 72a1056b1a866..b33d349cede28 100644
--- a/src/plugins/console/public/application/components/editor_example.tsx
+++ b/src/plugins/console/public/application/components/editor_example.tsx
@@ -27,13 +27,13 @@ interface EditorExampleProps {
const exampleText = `
# index a doc
-PUT index/1
+PUT index/_doc/1
{
"body": "here"
}
# and get it ...
-GET index/1
+GET index/_doc/1
`;
export function EditorExample(props: EditorExampleProps) {
diff --git a/src/plugins/data/common/field_formats/constants/base_formatters.ts b/src/plugins/data/common/field_formats/constants/base_formatters.ts
index 921c50571f727..99c24496cf220 100644
--- a/src/plugins/data/common/field_formats/constants/base_formatters.ts
+++ b/src/plugins/data/common/field_formats/constants/base_formatters.ts
@@ -23,7 +23,6 @@ import {
BoolFormat,
BytesFormat,
ColorFormat,
- DateNanosFormat,
DurationFormat,
IpFormat,
NumberFormat,
@@ -40,7 +39,6 @@ export const baseFormatters: FieldFormatInstanceType[] = [
BoolFormat,
BytesFormat,
ColorFormat,
- DateNanosFormat,
DurationFormat,
IpFormat,
NumberFormat,
diff --git a/src/plugins/data/common/field_formats/converters/date_nanos.test.ts b/src/plugins/data/common/field_formats/converters/date_nanos_shared.test.ts
similarity index 99%
rename from src/plugins/data/common/field_formats/converters/date_nanos.test.ts
rename to src/plugins/data/common/field_formats/converters/date_nanos_shared.test.ts
index 267f023e9b69d..6843427d273ff 100644
--- a/src/plugins/data/common/field_formats/converters/date_nanos.test.ts
+++ b/src/plugins/data/common/field_formats/converters/date_nanos_shared.test.ts
@@ -18,7 +18,7 @@
*/
import moment from 'moment-timezone';
-import { DateNanosFormat, analysePatternForFract, formatWithNanos } from './date_nanos';
+import { DateNanosFormat, analysePatternForFract, formatWithNanos } from './date_nanos_shared';
describe('Date Nanos Format', () => {
let convert: Function;
diff --git a/src/plugins/data/common/field_formats/converters/date_nanos.ts b/src/plugins/data/common/field_formats/converters/date_nanos_shared.ts
similarity index 93%
rename from src/plugins/data/common/field_formats/converters/date_nanos.ts
rename to src/plugins/data/common/field_formats/converters/date_nanos_shared.ts
index 3fa2b1c276cd7..89a63243c76f0 100644
--- a/src/plugins/data/common/field_formats/converters/date_nanos.ts
+++ b/src/plugins/data/common/field_formats/converters/date_nanos_shared.ts
@@ -18,11 +18,9 @@
*/
import { i18n } from '@kbn/i18n';
-import moment, { Moment } from 'moment';
import { memoize, noop } from 'lodash';
-import { KBN_FIELD_TYPES } from '../../kbn_field_types/types';
-import { FieldFormat } from '../field_format';
-import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types';
+import moment, { Moment } from 'moment';
+import { FieldFormat, FIELD_FORMAT_IDS, KBN_FIELD_TYPES, TextContextTypeConvert } from '../../';
/**
* Analyse the given moment.js format pattern for the fractional sec part (S,SS,SSS...)
@@ -76,9 +74,9 @@ export class DateNanosFormat extends FieldFormat {
});
static fieldType = KBN_FIELD_TYPES.DATE;
- private memoizedConverter: Function = noop;
- private memoizedPattern: string = '';
- private timeZone: string = '';
+ protected memoizedConverter: Function = noop;
+ protected memoizedPattern: string = '';
+ protected timeZone: string = '';
getParamDefaults() {
return {
diff --git a/src/plugins/data/common/field_formats/converters/index.ts b/src/plugins/data/common/field_formats/converters/index.ts
index cc9fae7fc9965..f71ddf5f781f7 100644
--- a/src/plugins/data/common/field_formats/converters/index.ts
+++ b/src/plugins/data/common/field_formats/converters/index.ts
@@ -19,7 +19,6 @@
export { UrlFormat } from './url';
export { BytesFormat } from './bytes';
-export { DateNanosFormat } from './date_nanos';
export { RelativeDateFormat } from './relative_date';
export { DurationFormat } from './duration';
export { IpFormat } from './ip';
diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts
index 74a942b51583d..84bedd2f9dee0 100644
--- a/src/plugins/data/common/field_formats/field_formats_registry.ts
+++ b/src/plugins/data/common/field_formats/field_formats_registry.ts
@@ -180,10 +180,18 @@ export class FieldFormatsRegistry {
* @param {ES_FIELD_TYPES[]} esTypes
* @return {FieldFormat}
*/
- getDefaultInstancePlain(fieldType: KBN_FIELD_TYPES, esTypes?: ES_FIELD_TYPES[]): FieldFormat {
+ getDefaultInstancePlain(
+ fieldType: KBN_FIELD_TYPES,
+ esTypes?: ES_FIELD_TYPES[],
+ params: Record = {}
+ ): FieldFormat {
const conf = this.getDefaultConfig(fieldType, esTypes);
+ const instanceParams = {
+ ...conf.params,
+ ...params,
+ };
- return this.getInstance(conf.id, conf.params);
+ return this.getInstance(conf.id, instanceParams);
}
/**
* Returns a cache key built by the given variables for caching in memoized
diff --git a/src/plugins/data/common/field_formats/index.ts b/src/plugins/data/common/field_formats/index.ts
index 104ff030873aa..d622af2f663a1 100644
--- a/src/plugins/data/common/field_formats/index.ts
+++ b/src/plugins/data/common/field_formats/index.ts
@@ -27,7 +27,6 @@ export {
BoolFormat,
BytesFormat,
ColorFormat,
- DateNanosFormat,
DurationFormat,
IpFormat,
NumberFormat,
diff --git a/src/plugins/data/public/field_formats/constants.ts b/src/plugins/data/public/field_formats/constants.ts
index a5c2b4e379908..d5e292c0e78e5 100644
--- a/src/plugins/data/public/field_formats/constants.ts
+++ b/src/plugins/data/public/field_formats/constants.ts
@@ -18,6 +18,6 @@
*/
import { baseFormatters } from '../../common';
-import { DateFormat } from './converters/date';
+import { DateFormat, DateNanosFormat } from './converters';
-export const baseFormattersPublic = [DateFormat, ...baseFormatters];
+export const baseFormattersPublic = [DateFormat, DateNanosFormat, ...baseFormatters];
diff --git a/src/legacy/core_plugins/timelion/public/shim/index.ts b/src/plugins/data/public/field_formats/converters/date_nanos.ts
similarity index 89%
rename from src/legacy/core_plugins/timelion/public/shim/index.ts
rename to src/plugins/data/public/field_formats/converters/date_nanos.ts
index cfc7b62ff4f86..d83926826011a 100644
--- a/src/legacy/core_plugins/timelion/public/shim/index.ts
+++ b/src/plugins/data/public/field_formats/converters/date_nanos.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export * from './legacy_dependencies_plugin';
+export { DateNanosFormat } from '../../../common/field_formats/converters/date_nanos_shared';
diff --git a/src/plugins/data/public/field_formats/converters/index.ts b/src/plugins/data/public/field_formats/converters/index.ts
index c51111092beca..f5f154084242f 100644
--- a/src/plugins/data/public/field_formats/converters/index.ts
+++ b/src/plugins/data/public/field_formats/converters/index.ts
@@ -18,3 +18,4 @@
*/
export { DateFormat } from './date';
+export { DateNanosFormat } from './date_nanos';
diff --git a/src/plugins/data/public/field_formats/index.ts b/src/plugins/data/public/field_formats/index.ts
index 015d5b39561bb..4525959fb864d 100644
--- a/src/plugins/data/public/field_formats/index.ts
+++ b/src/plugins/data/public/field_formats/index.ts
@@ -18,5 +18,5 @@
*/
export { FieldFormatsService, FieldFormatsSetup, FieldFormatsStart } from './field_formats_service';
-export { DateFormat } from './converters';
+export { DateFormat, DateNanosFormat } from './converters';
export { baseFormattersPublic } from './constants';
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index abec908b41c0f..2efd1c82aae79 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -157,7 +157,6 @@ import {
BoolFormat,
BytesFormat,
ColorFormat,
- DateNanosFormat,
DurationFormat,
IpFormat,
NumberFormat,
@@ -170,7 +169,7 @@ import {
TruncateFormat,
} from '../common/field_formats';
-import { DateFormat } from './field_formats';
+import { DateNanosFormat, DateFormat } from './field_formats';
export { baseFormattersPublic } from './field_formats';
// Field formats helpers namespace:
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index b532bacf5df25..0c23ba340304f 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -246,11 +246,12 @@ export class AggParamType extends Ba
makeAgg: (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig;
}
+// Warning: (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "DateFormat" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "baseFormattersPublic" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
-export const baseFormattersPublic: (import("../../common").FieldFormatInstanceType | typeof DateFormat)[];
+export const baseFormattersPublic: (import("../../common").FieldFormatInstanceType | typeof DateNanosFormat | typeof DateFormat)[];
// Warning: (ae-missing-release-tag) "BUCKET_TYPES" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -1955,42 +1956,41 @@ export const UI_SETTINGS: {
// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:370:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:370:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:370:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:370:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:372:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:373:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:176:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:232:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:371:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:372:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:381:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:382:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:41:60 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:53:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts
index cd5b4a2f724bd..c2434df3ae53c 100644
--- a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts
+++ b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts
@@ -111,9 +111,7 @@ describe('Top hit metric', () => {
it('requests both source and docvalues_fields for non-text aggregatable fields', () => {
init({ fieldName: 'bytes', readFromDocValues: true });
expect(aggDsl.top_hits._source).toBe('bytes');
- expect(aggDsl.top_hits.docvalue_fields).toEqual([
- { field: 'bytes', format: 'use_field_mapping' },
- ]);
+ expect(aggDsl.top_hits.docvalue_fields).toEqual([{ field: 'bytes' }]);
});
it('requests both source and docvalues_fields for date aggregatable fields', () => {
diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.ts
index 5ca883e60afd3..bee731dcc2e0d 100644
--- a/src/plugins/data/public/search/aggs/metrics/top_hit.ts
+++ b/src/plugins/data/public/search/aggs/metrics/top_hit.ts
@@ -88,12 +88,15 @@ export const getTopHitMetricAgg = () => {
};
} else {
if (field.readFromDocValues) {
- // always format date fields as date_time to avoid
- // displaying unformatted dates like epoch_millis
- // or other not-accepted momentjs formats
- const format =
- field.type === KBN_FIELD_TYPES.DATE ? 'date_time' : 'use_field_mapping';
- output.params.docvalue_fields = [{ field: field.name, format }];
+ output.params.docvalue_fields = [
+ {
+ field: field.name,
+ // always format date fields as date_time to avoid
+ // displaying unformatted dates like epoch_millis
+ // or other not-accepted momentjs formats
+ ...(field.type === KBN_FIELD_TYPES.DATE && { format: 'date_time' }),
+ },
+ ];
}
output.params._source = field.name === '_source' ? true : field.name;
}
diff --git a/src/plugins/data/server/field_formats/converters/date_nanos_server.test.ts b/src/plugins/data/server/field_formats/converters/date_nanos_server.test.ts
new file mode 100644
index 0000000000000..ba8e128f32728
--- /dev/null
+++ b/src/plugins/data/server/field_formats/converters/date_nanos_server.test.ts
@@ -0,0 +1,74 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { DateNanosFormat } from './date_nanos_server';
+import { FieldFormatsGetConfigFn } from 'src/plugins/data/common';
+
+describe('Date Nanos Format: Server side edition', () => {
+ let convert: Function;
+ let mockConfig: Record;
+ let getConfig: FieldFormatsGetConfigFn;
+
+ const dateTime = '2019-05-05T14:04:56.201900001Z';
+
+ beforeEach(() => {
+ mockConfig = {};
+ mockConfig.dateNanosFormat = 'MMMM Do YYYY, HH:mm:ss.SSSSSSSSS';
+ mockConfig['dateFormat:tz'] = 'Browser';
+
+ getConfig = (key: string) => mockConfig[key];
+ });
+
+ test('should format according to the given timezone parameter', () => {
+ const dateNy = new DateNanosFormat({ timezone: 'America/New_York' }, getConfig);
+ convert = dateNy.convert.bind(dateNy);
+ expect(convert(dateTime)).toMatchInlineSnapshot(`"May 5th 2019, 10:04:56.201900001"`);
+
+ const datePhx = new DateNanosFormat({ timezone: 'America/Phoenix' }, getConfig);
+ convert = datePhx.convert.bind(datePhx);
+ expect(convert(dateTime)).toMatchInlineSnapshot(`"May 5th 2019, 07:04:56.201900001"`);
+ });
+
+ test('should format according to UTC if no timezone parameter is given or exists in settings', () => {
+ const utcFormat = 'May 5th 2019, 14:04:56.201900001';
+ const dateUtc = new DateNanosFormat({ timezone: 'UTC' }, getConfig);
+ convert = dateUtc.convert.bind(dateUtc);
+ expect(convert(dateTime)).toBe(utcFormat);
+
+ const dateDefault = new DateNanosFormat({}, getConfig);
+ convert = dateDefault.convert.bind(dateDefault);
+ expect(convert(dateTime)).toBe(utcFormat);
+ });
+
+ test('should format according to dateFormat:tz if the setting is not "Browser"', () => {
+ mockConfig['dateFormat:tz'] = 'America/Phoenix';
+
+ const date = new DateNanosFormat({}, getConfig);
+ convert = date.convert.bind(date);
+ expect(convert(dateTime)).toMatchInlineSnapshot(`"May 5th 2019, 07:04:56.201900001"`);
+ });
+
+ test('should defer to meta params for timezone, not the UI config', () => {
+ mockConfig['dateFormat:tz'] = 'America/Phoenix';
+
+ const date = new DateNanosFormat({ timezone: 'America/New_York' }, getConfig);
+ convert = date.convert.bind(date);
+ expect(convert(dateTime)).toMatchInlineSnapshot(`"May 5th 2019, 10:04:56.201900001"`);
+ });
+});
diff --git a/src/plugins/data/server/field_formats/converters/date_nanos_server.ts b/src/plugins/data/server/field_formats/converters/date_nanos_server.ts
new file mode 100644
index 0000000000000..299b2aac93d49
--- /dev/null
+++ b/src/plugins/data/server/field_formats/converters/date_nanos_server.ts
@@ -0,0 +1,79 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { memoize } from 'lodash';
+import moment from 'moment-timezone';
+import {
+ analysePatternForFract,
+ DateNanosFormat,
+ formatWithNanos,
+} from '../../../common/field_formats/converters/date_nanos_shared';
+import { TextContextTypeConvert } from '../../../common';
+
+class DateNanosFormatServer extends DateNanosFormat {
+ textConvert: TextContextTypeConvert = (val) => {
+ // don't give away our ref to converter so
+ // we can hot-swap when config changes
+ const pattern = this.param('pattern');
+ const timezone = this.param('timezone');
+ const fractPattern = analysePatternForFract(pattern);
+ const fallbackPattern = this.param('patternFallback');
+
+ const timezoneChanged = this.timeZone !== timezone;
+ const datePatternChanged = this.memoizedPattern !== pattern;
+ if (timezoneChanged || datePatternChanged) {
+ this.timeZone = timezone;
+ this.memoizedPattern = pattern;
+
+ this.memoizedConverter = memoize((value: any) => {
+ if (value === null || value === undefined) {
+ return '-';
+ }
+
+ /* On the server, importing moment returns a new instance. Unlike on
+ * the client side, it doesn't have the dateFormat:tz configuration
+ * baked in.
+ * We need to set the timezone manually here. The date is taken in as
+ * UTC and converted into the desired timezone. */
+ let date;
+ if (this.timeZone === 'Browser') {
+ // Assume a warning has been logged that this can be unpredictable. It
+ // would be too verbose to log anything here.
+ date = moment.utc(val);
+ } else {
+ date = moment.utc(val).tz(this.timeZone);
+ }
+
+ if (typeof value !== 'string' && date.isValid()) {
+ // fallback for max/min aggregation, where unixtime in ms is returned as a number
+ // aggregations in Elasticsearch generally just return ms
+ return date.format(fallbackPattern);
+ } else if (date.isValid()) {
+ return formatWithNanos(date, value, fractPattern);
+ } else {
+ return value;
+ }
+ });
+ }
+
+ return this.memoizedConverter(val);
+ };
+}
+
+export { DateNanosFormatServer as DateNanosFormat };
diff --git a/src/plugins/data/server/field_formats/converters/index.ts b/src/plugins/data/server/field_formats/converters/index.ts
index f5c69df972869..1c6b827e2fbb5 100644
--- a/src/plugins/data/server/field_formats/converters/index.ts
+++ b/src/plugins/data/server/field_formats/converters/index.ts
@@ -18,3 +18,4 @@
*/
export { DateFormat } from './date_server';
+export { DateNanosFormat } from './date_nanos_server';
diff --git a/src/plugins/data/server/field_formats/field_formats_service.ts b/src/plugins/data/server/field_formats/field_formats_service.ts
index 70584efbee0a0..cafb88de4b893 100644
--- a/src/plugins/data/server/field_formats/field_formats_service.ts
+++ b/src/plugins/data/server/field_formats/field_formats_service.ts
@@ -23,10 +23,14 @@ import {
baseFormatters,
} from '../../common/field_formats';
import { IUiSettingsClient } from '../../../../core/server';
-import { DateFormat } from './converters';
+import { DateFormat, DateNanosFormat } from './converters';
export class FieldFormatsService {
- private readonly fieldFormatClasses: FieldFormatInstanceType[] = [DateFormat, ...baseFormatters];
+ private readonly fieldFormatClasses: FieldFormatInstanceType[] = [
+ DateFormat,
+ DateNanosFormat,
+ ...baseFormatters,
+ ];
public setup() {
return {
diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts
index 0dd0115add8ad..321bd913ce760 100644
--- a/src/plugins/data/server/index.ts
+++ b/src/plugins/data/server/index.ts
@@ -86,7 +86,6 @@ import {
BoolFormat,
BytesFormat,
ColorFormat,
- DateNanosFormat,
DurationFormat,
IpFormat,
NumberFormat,
@@ -105,7 +104,6 @@ export const fieldFormats = {
BoolFormat,
BytesFormat,
ColorFormat,
- DateNanosFormat,
DurationFormat,
IpFormat,
NumberFormat,
@@ -166,15 +164,10 @@ import {
export { ParsedInterval } from '../common';
export {
- ISearch,
- ISearchCancel,
+ ISearchStrategy,
ISearchOptions,
- IRequestTypesMap,
- IResponseTypesMap,
ISearchSetup,
ISearchStart,
- TStrategyTypes,
- ISearchStrategy,
getDefaultSearchParams,
getTotalLoaded,
} from './search';
diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts
index db08ddf920818..82f8ef21ebb38 100644
--- a/src/plugins/data/server/search/es_search/es_search_strategy.ts
+++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts
@@ -17,17 +17,16 @@
* under the License.
*/
import { first } from 'rxjs/operators';
-import { RequestHandlerContext, SharedGlobalConfig } from 'kibana/server';
+import { SharedGlobalConfig } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import { Observable } from 'rxjs';
-import { ES_SEARCH_STRATEGY } from '../../../common/search';
import { ISearchStrategy, getDefaultSearchParams, getTotalLoaded } from '..';
export const esSearchStrategyProvider = (
config$: Observable
-): ISearchStrategy => {
+): ISearchStrategy => {
return {
- search: async (context: RequestHandlerContext, request, options) => {
+ search: async (context, request, options) => {
const config = await config$.pipe(first()).toPromise();
const defaultParams = getDefaultSearchParams(config);
diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts
index 882f56e83d4ca..67789fcbf56b4 100644
--- a/src/plugins/data/server/search/index.ts
+++ b/src/plugins/data/server/search/index.ts
@@ -17,16 +17,6 @@
* under the License.
*/
-export {
- ISearch,
- ISearchCancel,
- ISearchOptions,
- IRequestTypesMap,
- IResponseTypesMap,
- ISearchSetup,
- ISearchStart,
- TStrategyTypes,
- ISearchStrategy,
-} from './types';
+export { ISearchStrategy, ISearchOptions, ISearchSetup, ISearchStart } from './types';
export { getDefaultSearchParams, getTotalLoaded } from './es_search';
diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts
index 0aab466a9a0d9..b210df3c55db9 100644
--- a/src/plugins/data/server/search/mocks.ts
+++ b/src/plugins/data/server/search/mocks.ts
@@ -26,5 +26,6 @@ export function createSearchSetupMock() {
export function createSearchStartMock() {
return {
getSearchStrategy: jest.fn(),
+ search: jest.fn(),
};
}
diff --git a/src/plugins/data/server/search/routes.test.ts b/src/plugins/data/server/search/routes.test.ts
index 4ef67de93e454..167bd5af5d51d 100644
--- a/src/plugins/data/server/search/routes.test.ts
+++ b/src/plugins/data/server/search/routes.test.ts
@@ -33,9 +33,8 @@ describe('Search service', () => {
});
it('handler calls context.search.search with the given request and strategy', async () => {
- const mockSearch = jest.fn().mockResolvedValue('yay');
- mockDataStart.search.getSearchStrategy.mockReturnValueOnce({ search: mockSearch });
-
+ const response = { id: 'yay' };
+ mockDataStart.search.search.mockResolvedValue(response);
const mockContext = {};
const mockBody = { params: {} };
const mockParams = { strategy: 'foo' };
@@ -51,21 +50,21 @@ describe('Search service', () => {
const handler = mockRouter.post.mock.calls[0][1];
await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse);
- expect(mockDataStart.search.getSearchStrategy.mock.calls[0][0]).toBe(mockParams.strategy);
- expect(mockSearch).toBeCalled();
- expect(mockSearch.mock.calls[0][1]).toStrictEqual(mockBody);
+ expect(mockDataStart.search.search).toBeCalled();
+ expect(mockDataStart.search.search.mock.calls[0][1]).toStrictEqual(mockBody);
expect(mockResponse.ok).toBeCalled();
- expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: 'yay' });
+ expect(mockResponse.ok.mock.calls[0][0]).toEqual({
+ body: response,
+ });
});
it('handler throws an error if the search throws an error', async () => {
- const mockSearch = jest.fn().mockRejectedValue({
+ mockDataStart.search.search.mockRejectedValue({
message: 'oh no',
body: {
error: 'oops',
},
});
- mockDataStart.search.getSearchStrategy.mockReturnValueOnce({ search: mockSearch });
const mockContext = {};
const mockBody = { params: {} };
@@ -82,9 +81,8 @@ describe('Search service', () => {
const handler = mockRouter.post.mock.calls[0][1];
await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse);
- expect(mockDataStart.search.getSearchStrategy.mock.calls[0][0]).toBe(mockParams.strategy);
- expect(mockSearch).toBeCalled();
- expect(mockSearch.mock.calls[0][1]).toStrictEqual(mockBody);
+ expect(mockDataStart.search.search).toBeCalled();
+ expect(mockDataStart.search.search.mock.calls[0][1]).toStrictEqual(mockBody);
expect(mockResponse.customError).toBeCalled();
const error: any = mockResponse.customError.mock.calls[0][0];
expect(error.body.message).toBe('oh no');
diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts
index 7b6c045b0908c..bf1982a1f7fb2 100644
--- a/src/plugins/data/server/search/routes.ts
+++ b/src/plugins/data/server/search/routes.ts
@@ -42,10 +42,12 @@ export function registerSearchRoute(core: CoreSetup