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/settings/ingest-manager-settings.asciidoc b/docs/settings/ingest-manager-settings.asciidoc
index f46c769079040..604471edc4d59 100644
--- a/docs/settings/ingest-manager-settings.asciidoc
+++ b/docs/settings/ingest-manager-settings.asciidoc
@@ -20,8 +20,6 @@ See the {ingest-guide}/index.html[Ingest Management] docs for more information.
|===
| `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}.
| `xpack.ingestManager.fleet.enabled` {ess-icon}
| Set to `true` (default) to enable {fleet}.
|===
@@ -32,7 +30,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-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/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/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/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/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/plugins/data/public/field_formats/converters/date_nanos.ts b/src/plugins/data/public/field_formats/converters/date_nanos.ts
new file mode 100644
index 0000000000000..d83926826011a
--- /dev/null
+++ b/src/plugins/data/public/field_formats/converters/date_nanos.ts
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+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/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..b94238dcf96a4 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,
diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md
index 6b62d942de688..1fe03119c789d 100644
--- a/src/plugins/data/server/server.api.md
+++ b/src/plugins/data/server/server.api.md
@@ -295,7 +295,6 @@ export const fieldFormats: {
BoolFormat: typeof BoolFormat;
BytesFormat: typeof BytesFormat;
ColorFormat: typeof ColorFormat;
- DateNanosFormat: typeof DateNanosFormat;
DurationFormat: typeof DurationFormat;
IpFormat: typeof IpFormat;
NumberFormat: typeof NumberFormat;
@@ -804,31 +803,30 @@ export const UI_SETTINGS: {
// src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "FieldFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:129:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:129:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:189:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:190:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:193:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "FieldFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:183:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:191:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)
diff --git a/src/plugins/maps_legacy/server/index.ts b/src/plugins/maps_legacy/server/index.ts
index 18f58189fc607..5da3ce1a84408 100644
--- a/src/plugins/maps_legacy/server/index.ts
+++ b/src/plugins/maps_legacy/server/index.ts
@@ -17,8 +17,9 @@
* under the License.
*/
-import { PluginConfigDescriptor } from 'kibana/server';
-import { PluginInitializerContext } from 'kibana/public';
+import { Plugin, PluginConfigDescriptor } from 'kibana/server';
+import { PluginInitializerContext } from 'src/core/server';
+import { Observable } from 'rxjs';
import { configSchema, ConfigSchema } from '../config';
export const config: PluginConfigDescriptor = {
@@ -37,13 +38,27 @@ export const config: PluginConfigDescriptor = {
schema: configSchema,
};
-export const plugin = (initializerContext: PluginInitializerContext) => ({
- setup() {
+export interface MapsLegacyPluginSetup {
+ config$: Observable;
+}
+
+export class MapsLegacyPlugin implements Plugin {
+ readonly _initializerContext: PluginInitializerContext;
+
+ constructor(initializerContext: PluginInitializerContext) {
+ this._initializerContext = initializerContext;
+ }
+
+ public setup() {
// @ts-ignore
- const config$ = initializerContext.config.create();
+ const config$ = this._initializerContext.config.create();
return {
- config: config$,
+ config$,
};
- },
- start() {},
-});
+ }
+
+ public start() {}
+}
+
+export const plugin = (initializerContext: PluginInitializerContext) =>
+ new MapsLegacyPlugin(initializerContext);
diff --git a/test/api_integration/apis/saved_objects/bulk_create.js b/test/api_integration/apis/saved_objects/bulk_create.js
index 6cb9d5dccdc9a..7db968df8357a 100644
--- a/test/api_integration/apis/saved_objects/bulk_create.js
+++ b/test/api_integration/apis/saved_objects/bulk_create.js
@@ -76,6 +76,7 @@ export default function ({ getService }) {
dashboard: resp.body.saved_objects[1].migrationVersion.dashboard,
},
references: [],
+ namespaces: ['default'],
},
],
});
@@ -121,6 +122,7 @@ export default function ({ getService }) {
title: 'An existing visualization',
},
references: [],
+ namespaces: ['default'],
migrationVersion: {
visualization: resp.body.saved_objects[0].migrationVersion.visualization,
},
@@ -134,6 +136,7 @@ export default function ({ getService }) {
title: 'A great new dashboard',
},
references: [],
+ namespaces: ['default'],
migrationVersion: {
dashboard: resp.body.saved_objects[1].migrationVersion.dashboard,
},
diff --git a/test/api_integration/apis/saved_objects/bulk_get.js b/test/api_integration/apis/saved_objects/bulk_get.js
index c802d52913065..56ee5a69be23e 100644
--- a/test/api_integration/apis/saved_objects/bulk_get.js
+++ b/test/api_integration/apis/saved_objects/bulk_get.js
@@ -68,6 +68,7 @@ export default function ({ getService }) {
resp.body.saved_objects[0].attributes.kibanaSavedObjectMeta,
},
migrationVersion: resp.body.saved_objects[0].migrationVersion,
+ namespaces: ['default'],
references: [
{
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
@@ -94,6 +95,7 @@ export default function ({ getService }) {
buildNum: 8467,
defaultIndex: '91200a00-9efd-11e7-acb3-3dab96693fab',
},
+ namespaces: ['default'],
migrationVersion: resp.body.saved_objects[2].migrationVersion,
references: [],
},
diff --git a/test/api_integration/apis/saved_objects/bulk_update.js b/test/api_integration/apis/saved_objects/bulk_update.js
index e3f994ff224e8..973ce382ea813 100644
--- a/test/api_integration/apis/saved_objects/bulk_update.js
+++ b/test/api_integration/apis/saved_objects/bulk_update.js
@@ -65,6 +65,7 @@ export default function ({ getService }) {
attributes: {
title: 'An existing visualization',
},
+ namespaces: ['default'],
});
expect(secondObject)
@@ -77,6 +78,7 @@ export default function ({ getService }) {
attributes: {
title: 'An existing dashboard',
},
+ namespaces: ['default'],
});
});
@@ -233,6 +235,7 @@ export default function ({ getService }) {
attributes: {
title: 'An existing dashboard',
},
+ namespaces: ['default'],
});
});
});
diff --git a/test/api_integration/apis/saved_objects/create.js b/test/api_integration/apis/saved_objects/create.js
index eddda3aded141..c1300125441bc 100644
--- a/test/api_integration/apis/saved_objects/create.js
+++ b/test/api_integration/apis/saved_objects/create.js
@@ -58,6 +58,7 @@ export default function ({ getService }) {
title: 'My favorite vis',
},
references: [],
+ namespaces: ['default'],
});
expect(resp.body.migrationVersion).to.be.ok();
});
@@ -104,6 +105,7 @@ export default function ({ getService }) {
title: 'My favorite vis',
},
references: [],
+ namespaces: ['default'],
});
expect(resp.body.migrationVersion).to.be.ok();
});
diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js
index 7cb5955e4a43d..f129bf22840da 100644
--- a/test/api_integration/apis/saved_objects/find.js
+++ b/test/api_integration/apis/saved_objects/find.js
@@ -48,6 +48,7 @@ export default function ({ getService }) {
},
score: 0,
migrationVersion: resp.body.saved_objects[0].migrationVersion,
+ namespaces: ['default'],
references: [
{
id: '91200a00-9efd-11e7-acb3-3dab96693fab',
@@ -107,6 +108,93 @@ export default function ({ getService }) {
}));
});
+ describe('unknown namespace', () => {
+ it('should return 200 with empty response', async () =>
+ await supertest
+ .get('/api/saved_objects/_find?type=visualization&namespaces=foo')
+ .expect(200)
+ .then((resp) => {
+ expect(resp.body).to.eql({
+ page: 1,
+ per_page: 20,
+ total: 0,
+ saved_objects: [],
+ });
+ }));
+ });
+
+ describe('known namespace', () => {
+ it('should return 200 with individual responses', async () =>
+ await supertest
+ .get('/api/saved_objects/_find?type=visualization&fields=title&namespaces=default')
+ .expect(200)
+ .then((resp) => {
+ expect(resp.body).to.eql({
+ page: 1,
+ per_page: 20,
+ total: 1,
+ saved_objects: [
+ {
+ type: 'visualization',
+ id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
+ version: 'WzIsMV0=',
+ attributes: {
+ title: 'Count of requests',
+ },
+ migrationVersion: resp.body.saved_objects[0].migrationVersion,
+ namespaces: ['default'],
+ score: 0,
+ references: [
+ {
+ id: '91200a00-9efd-11e7-acb3-3dab96693fab',
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ },
+ ],
+ updated_at: '2017-09-21T18:51:23.794Z',
+ },
+ ],
+ });
+ expect(resp.body.saved_objects[0].migrationVersion).to.be.ok();
+ }));
+ });
+
+ describe('wildcard namespace', () => {
+ it('should return 200 with individual responses from the default namespace', async () =>
+ await supertest
+ .get('/api/saved_objects/_find?type=visualization&fields=title&namespaces=*')
+ .expect(200)
+ .then((resp) => {
+ expect(resp.body).to.eql({
+ page: 1,
+ per_page: 20,
+ total: 1,
+ saved_objects: [
+ {
+ type: 'visualization',
+ id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
+ version: 'WzIsMV0=',
+ attributes: {
+ title: 'Count of requests',
+ },
+ migrationVersion: resp.body.saved_objects[0].migrationVersion,
+ namespaces: ['default'],
+ score: 0,
+ references: [
+ {
+ id: '91200a00-9efd-11e7-acb3-3dab96693fab',
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ },
+ ],
+ updated_at: '2017-09-21T18:51:23.794Z',
+ },
+ ],
+ });
+ expect(resp.body.saved_objects[0].migrationVersion).to.be.ok();
+ }));
+ });
+
describe('with a filter', () => {
it('should return 200 with a valid response', async () =>
await supertest
@@ -135,6 +223,7 @@ export default function ({ getService }) {
.searchSourceJSON,
},
},
+ namespaces: ['default'],
score: 0,
references: [
{
diff --git a/test/api_integration/apis/saved_objects/get.js b/test/api_integration/apis/saved_objects/get.js
index 55dfda251a75a..6bb5cf0c8a7ff 100644
--- a/test/api_integration/apis/saved_objects/get.js
+++ b/test/api_integration/apis/saved_objects/get.js
@@ -56,6 +56,7 @@ export default function ({ getService }) {
id: '91200a00-9efd-11e7-acb3-3dab96693fab',
},
],
+ namespaces: ['default'],
});
expect(resp.body.migrationVersion).to.be.ok();
}));
diff --git a/test/api_integration/apis/saved_objects/update.js b/test/api_integration/apis/saved_objects/update.js
index d613f46878bb5..7803c39897f28 100644
--- a/test/api_integration/apis/saved_objects/update.js
+++ b/test/api_integration/apis/saved_objects/update.js
@@ -56,6 +56,7 @@ export default function ({ getService }) {
attributes: {
title: 'My second favorite vis',
},
+ namespaces: ['default'],
});
});
});
diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts
index b5154d619685a..08c4327d7c0c4 100644
--- a/test/api_integration/apis/saved_objects_management/find.ts
+++ b/test/api_integration/apis/saved_objects_management/find.ts
@@ -49,6 +49,7 @@ export default function ({ getService }: FtrProviderContext) {
title: 'Count of requests',
},
migrationVersion: resp.body.saved_objects[0].migrationVersion,
+ namespaces: ['default'],
references: [
{
id: '91200a00-9efd-11e7-acb3-3dab96693fab',
diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy
index f3fc5f84583c9..f43fe9f96c3ef 100644
--- a/vars/kibanaPipeline.groovy
+++ b/vars/kibanaPipeline.groovy
@@ -209,7 +209,7 @@ def runErrorReporter() {
bash(
"""
source src/dev/ci_setup/setup_env.sh
- node scripts/report_failed_tests ${dryRun}
+ node scripts/report_failed_tests ${dryRun} target/junit/**/*.xml
""",
"Report failed tests, if necessary"
)
diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md
index 9e07727204f88..e6b22da7a1fe3 100644
--- a/x-pack/plugins/actions/README.md
+++ b/x-pack/plugins/actions/README.md
@@ -437,9 +437,12 @@ The config and params properties are modelled after the [Watcher Index Action](h
### `config`
-| Property | Description | Type |
-| -------- | -------------------------------------- | ------------------- |
-| index | The Elasticsearch index to index into. | string _(optional)_ |
+| Property | Description | Type |
+| -------------------- | ---------------------------------------------------------- | -------------------- |
+| index | The Elasticsearch index to index into. | string _(optional)_ |
+| doc_id | The optional \_id of the document. | string _(optional)_ |
+| execution_time_field | The field that will store/index the action execution time. | string _(optional)_ |
+| refresh | Setting of the refresh policy for the write request. | boolean _(optional)_ |
### `secrets`
@@ -447,13 +450,9 @@ This action type has no `secrets` properties.
### `params`
-| Property | Description | Type |
-| -------------------- | ---------------------------------------------------------- | -------------------- |
-| index | The Elasticsearch index to index into. | string _(optional)_ |
-| doc_id | The optional \_id of the document. | string _(optional)_ |
-| execution_time_field | The field that will store/index the action execution time. | string _(optional)_ |
-| refresh | Setting of the refresh policy for the write request | boolean _(optional)_ |
-| body | The documument body/bodies to index. | object or object[] |
+| Property | Description | Type |
+| --------- | ---------------------------------------- | ------------------- |
+| documents | JSON object that describes the [document](https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-index.html#getting-started-batch-processing). | object[] |
---
diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.test.tsx
new file mode 100644
index 0000000000000..268d8bd7ea823
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.test.tsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { showAlert } from './AnomalyDetectionSetupLink';
+
+describe('#showAlert', () => {
+ describe('when an environment is selected', () => {
+ it('should return true when there are no jobs', () => {
+ const result = showAlert([], 'testing');
+ expect(result).toBe(true);
+ });
+ it('should return true when environment is not included in the jobs', () => {
+ const result = showAlert(
+ [{ environment: 'staging' }, { environment: 'production' }],
+ 'testing'
+ );
+ expect(result).toBe(true);
+ });
+ it('should return false when environment is included in the jobs', () => {
+ const result = showAlert(
+ [{ environment: 'staging' }, { environment: 'production' }],
+ 'staging'
+ );
+ expect(result).toBe(false);
+ });
+ });
+ describe('there is no environment selected (All)', () => {
+ it('should return true when there are no jobs', () => {
+ const result = showAlert([], undefined);
+ expect(result).toBe(true);
+ });
+ it('should return false when there are any number of jobs', () => {
+ const result = showAlert(
+ [{ environment: 'staging' }, { environment: 'production' }],
+ undefined
+ );
+ expect(result).toBe(false);
+ });
+ });
+});
diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx
index 88d15239b8fba..6f3a5df480d7e 100644
--- a/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Links/apm/AnomalyDetectionSetupLink.tsx
@@ -23,16 +23,12 @@ export function AnomalyDetectionSetupLink() {
);
const isFetchSuccess = status === FETCH_STATUS.SUCCESS;
- // Show alert if there are no jobs OR if no job matches the current environment
- const showAlert =
- isFetchSuccess && !data.jobs.some((job) => environment === job.environment);
-
return (
{ANOMALY_DETECTION_LINK_LABEL}
- {showAlert && (
+ {isFetchSuccess && showAlert(data.jobs, environment) && (
@@ -61,3 +57,16 @@ const ANOMALY_DETECTION_LINK_LABEL = i18n.translate(
'xpack.apm.anomalyDetectionSetup.linkLabel',
{ defaultMessage: `Anomaly detection` }
);
+
+export function showAlert(
+ jobs: Array<{ environment: string }> = [],
+ environment: string | undefined
+) {
+ return (
+ // No job exists, or
+ jobs.length === 0 ||
+ // no job exists for the selected environment
+ (environment !== undefined &&
+ jobs.every((job) => environment !== job.environment))
+ );
+}
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts
index 2f5e703251c03..154821b261fd1 100644
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts
@@ -31,7 +31,7 @@ export async function getMlBucketSize({
body: {
_source: 'bucket_span',
size: 1,
- terminateAfter: 1,
+ terminate_after: 1,
query: {
bool: {
filter: [
diff --git a/x-pack/plugins/canvas/.storybook/storyshots.test.js b/x-pack/plugins/canvas/.storybook/storyshots.test.js
index a3412c3a14e79..7195b97712464 100644
--- a/x-pack/plugins/canvas/.storybook/storyshots.test.js
+++ b/x-pack/plugins/canvas/.storybook/storyshots.test.js
@@ -84,6 +84,10 @@ import { RenderedElement } from '../shareable_runtime/components/rendered_elemen
jest.mock('../shareable_runtime/components/rendered_element');
RenderedElement.mockImplementation(() => 'RenderedElement');
+import { EuiObserver } from '@elastic/eui/test-env/components/observer/observer';
+jest.mock('@elastic/eui/test-env/components/observer/observer');
+EuiObserver.mockImplementation(() => 'EuiObserver');
+
addSerializer(styleSheetSerializer);
// Initialize Storyshots and build the Jest Snapshots
diff --git a/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts b/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts
index 271fc7a979057..4b1f31cb14687 100644
--- a/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts
+++ b/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts
@@ -25,6 +25,7 @@ const BaseWorkpad: CanvasWorkpad = {
pages: [],
colors: [],
isWriteable: true,
+ variables: [],
};
const BasePage: CanvasPage = {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js
index 7384986fa5c2b..618fe756ba0a4 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js
@@ -107,7 +107,7 @@ const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => {
diff --git a/x-pack/plugins/canvas/i18n/components.ts b/x-pack/plugins/canvas/i18n/components.ts
index 8acda5da4f0d2..78083f26a38b1 100644
--- a/x-pack/plugins/canvas/i18n/components.ts
+++ b/x-pack/plugins/canvas/i18n/components.ts
@@ -545,7 +545,7 @@ export const ComponentStrings = {
}),
getTitle: () =>
i18n.translate('xpack.canvas.pageConfig.title', {
- defaultMessage: 'Page styles',
+ defaultMessage: 'Page settings',
}),
getTransitionLabel: () =>
i18n.translate('xpack.canvas.pageConfig.transitionLabel', {
@@ -899,6 +899,144 @@ export const ComponentStrings = {
defaultMessage: 'Close tray',
}),
},
+ VarConfig: {
+ getAddButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.addButtonLabel', {
+ defaultMessage: 'Add a variable',
+ }),
+ getAddTooltipLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.addTooltipLabel', {
+ defaultMessage: 'Add a variable',
+ }),
+ getCopyActionButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.copyActionButtonLabel', {
+ defaultMessage: 'Copy snippet',
+ }),
+ getCopyActionTooltipLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.copyActionTooltipLabel', {
+ defaultMessage: 'Copy variable syntax to clipboard',
+ }),
+ getCopyNotificationDescription: () =>
+ i18n.translate('xpack.canvas.varConfig.copyNotificationDescription', {
+ defaultMessage: 'Variable syntax copied to clipboard',
+ }),
+ getDeleteActionButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.deleteActionButtonLabel', {
+ defaultMessage: 'Delete variable',
+ }),
+ getDeleteNotificationDescription: () =>
+ i18n.translate('xpack.canvas.varConfig.deleteNotificationDescription', {
+ defaultMessage: 'Variable successfully deleted',
+ }),
+ getEditActionButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.editActionButtonLabel', {
+ defaultMessage: 'Edit variable',
+ }),
+ getEmptyDescription: () =>
+ i18n.translate('xpack.canvas.varConfig.emptyDescription', {
+ defaultMessage:
+ 'This workpad has no variables currently. You may add variables to store and edit common values. These variables can then be used in elements or within the expression editor.',
+ }),
+ getTableNameLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.tableNameLabel', {
+ defaultMessage: 'Name',
+ }),
+ getTableTypeLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.tableTypeLabel', {
+ defaultMessage: 'Type',
+ }),
+ getTableValueLabel: () =>
+ i18n.translate('xpack.canvas.varConfig.tableValueLabel', {
+ defaultMessage: 'Value',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.varConfig.titleLabel', {
+ defaultMessage: 'Variables',
+ }),
+ getTitleTooltip: () =>
+ i18n.translate('xpack.canvas.varConfig.titleTooltip', {
+ defaultMessage: 'Add variables to store and edit common values',
+ }),
+ },
+ VarConfigDeleteVar: {
+ getCancelButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfigDeleteVar.cancelButtonLabel', {
+ defaultMessage: 'Cancel',
+ }),
+ getDeleteButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfigDeleteVar.deleteButtonLabel', {
+ defaultMessage: 'Delete variable',
+ }),
+ getTitle: () =>
+ i18n.translate('xpack.canvas.varConfigDeleteVar.titleLabel', {
+ defaultMessage: 'Delete variable?',
+ }),
+ getWarningDescription: () =>
+ i18n.translate('xpack.canvas.varConfigDeleteVar.warningDescription', {
+ defaultMessage:
+ 'Deleting this variable may adversely affect the workpad. Are you sure you wish to continue?',
+ }),
+ },
+ VarConfigEditVar: {
+ getAddTitle: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.addTitleLabel', {
+ defaultMessage: 'Add variable',
+ }),
+ getCancelButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.cancelButtonLabel', {
+ defaultMessage: 'Cancel',
+ }),
+ getDuplicateNameError: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.duplicateNameError', {
+ defaultMessage: 'Variable name already in use',
+ }),
+ getEditTitle: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.editTitleLabel', {
+ defaultMessage: 'Edit variable',
+ }),
+ getEditWarning: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.editWarning', {
+ defaultMessage: 'Editing a variable in use may adversely affect your workpad',
+ }),
+ getNameFieldLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.nameFieldLabel', {
+ defaultMessage: 'Name',
+ }),
+ getSaveButtonLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.saveButtonLabel', {
+ defaultMessage: 'Save changes',
+ }),
+ getTypeBooleanLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.typeBooleanLabel', {
+ defaultMessage: 'Boolean',
+ }),
+ getTypeFieldLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.typeFieldLabel', {
+ defaultMessage: 'Type',
+ }),
+ getTypeNumberLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.typeNumberLabel', {
+ defaultMessage: 'Number',
+ }),
+ getTypeStringLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.typeStringLabel', {
+ defaultMessage: 'String',
+ }),
+ getValueFieldLabel: () =>
+ i18n.translate('xpack.canvas.varConfigEditVar.valueFieldLabel', {
+ defaultMessage: 'Value',
+ }),
+ },
+ VarConfigVarValueField: {
+ getFalseOption: () =>
+ i18n.translate('xpack.canvas.varConfigVarValueField.falseOption', {
+ defaultMessage: 'False',
+ }),
+ getTrueOption: () =>
+ i18n.translate('xpack.canvas.varConfigVarValueField.trueOption', {
+ defaultMessage: 'True',
+ }),
+ },
WorkpadConfig: {
getApplyStylesheetButtonLabel: () =>
i18n.translate('xpack.canvas.workpadConfig.applyStylesheetButtonLabel', {
diff --git a/x-pack/plugins/canvas/public/components/arg_form/arg_form.js b/x-pack/plugins/canvas/public/components/arg_form/arg_form.js
index dfd99b18646a6..f356eedff19cf 100644
--- a/x-pack/plugins/canvas/public/components/arg_form/arg_form.js
+++ b/x-pack/plugins/canvas/public/components/arg_form/arg_form.js
@@ -120,7 +120,7 @@ class ArgFormComponent extends PureComponent {
);
return (
-