diff --git a/NOTICE.txt b/NOTICE.txt
index 4eec329b7a603..4ede43610ca7b 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -149,17 +149,17 @@ SOFTWARE.
---
Detection Rules
-Copyright 2020 Elasticsearch B.V.
+Copyright 2021 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:
+which is available under a "MIT" license. The rules 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
+- "Potential Evasion via Filter Manager" (06dceabf-adca-48af-ac79-ffdf4c3b1e9a)
+- "Process Discovery via Tasklist" (cc16f774-59f9-462d-8b98-d27ccd4519ec)
+- "Potential Modification of Accessibility Binaries" (7405ddf1-6c8e-41ce-818f-48bea6bcaed8)
+- "Potential Application Shimming via Sdbinst" (fd4a992d-6130-4802-9ff8-829b89ae801f)
+- "Trusted Developer Application Usage" (9d110cb3-5f4b-4c9a-b9f5-53f0a1707ae1)
MIT License
@@ -185,9 +185,9 @@ 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:
+which is available under a "MIT" license. The rules based on this license are:
-- credential_access_secretsmanager_getsecretvalue.toml
+- "AWS Access Secret in Secrets Manager" (a00681e3-9ed6-447c-ab2c-be648821c622)
MIT License
@@ -235,6 +235,10 @@ 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.
+---
+Portions of this code are licensed under the following license:
+For license information please see https://edge.fullstory.com/s/fs.js.LICENSE.txt
+
---
This product bundles bootstrap@3.3.6 which is available under a
"MIT" license.
diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc
index 5d7ba22841aa1..217645b903818 100644
--- a/docs/developer/getting-started/monorepo-packages.asciidoc
+++ b/docs/developer/getting-started/monorepo-packages.asciidoc
@@ -70,6 +70,7 @@ yarn kbn watch-bazel
- @kbn/apm-utils
- @kbn/babel-code-parser
- @kbn/babel-preset
+- @kbn/cli-dev-mode
- @kbn/config
- @kbn/config-schema
- @kbn/crypto
@@ -87,6 +88,7 @@ yarn kbn watch-bazel
- @kbn/mapbox-gl
- @kbn/monaco
- @kbn/optimizer
+- @kbn/plugin-helpers
- @kbn/rule-data-utils
- @kbn/securitysolution-es-utils
- @kbn/securitysolution-hook-utils
diff --git a/docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md b/docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md
new file mode 100644
index 0000000000000..217066481d33c
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CspConfig](./kibana-plugin-core-server.cspconfig.md) > ["\#private"](./kibana-plugin-core-server.cspconfig.__private_.md)
+
+## CspConfig."\#private" property
+
+Signature:
+
+```typescript
+#private;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.cspconfig.md b/docs/development/core/server/kibana-plugin-core-server.cspconfig.md
index 9f4f3211ea2b1..0337a1f4d3301 100644
--- a/docs/development/core/server/kibana-plugin-core-server.cspconfig.md
+++ b/docs/development/core/server/kibana-plugin-core-server.cspconfig.md
@@ -20,6 +20,7 @@ The constructor for this class is marked as internal. Third-party code should no
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
+| ["\#private"](./kibana-plugin-core-server.cspconfig.__private_.md) | | | |
| [DEFAULT](./kibana-plugin-core-server.cspconfig.default.md) | static | CspConfig | |
| [disableEmbedding](./kibana-plugin-core-server.cspconfig.disableembedding.md) | | boolean | |
| [header](./kibana-plugin-core-server.cspconfig.header.md) | | string | |
diff --git a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.set.md b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.set.md
index 143cd397c40ae..bf08ca1682f3b 100644
--- a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.set.md
+++ b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.set.md
@@ -24,5 +24,7 @@ set(status$: Observable): void;
## Remarks
+The first emission from this Observable should occur within 30s, else this plugin's status will fallback to `unavailable` until the first emission.
+
See the [StatusServiceSetup.derivedStatus$](./kibana-plugin-core-server.statusservicesetup.derivedstatus_.md) API for leveraging the default status calculation that is provided by Core.
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md
index 54b5a33ccf682..2ca4847d6dc39 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md
@@ -13,11 +13,11 @@ esFilters: {
FILTERS: typeof FILTERS;
FilterStateStore: typeof FilterStateStore;
buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter;
- buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IIndexPattern) => import("../common").PhrasesFilter;
- buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IIndexPattern) => import("../common").ExistsFilter;
- buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IIndexPattern) => import("../common").PhraseFilter;
+ buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").MinimalIndexPattern) => import("../common").PhrasesFilter;
+ buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").MinimalIndexPattern) => import("../common").ExistsFilter;
+ buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").MinimalIndexPattern) => import("../common").PhraseFilter;
buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter;
- buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IIndexPattern, formattedValue?: string | undefined) => import("../common").RangeFilter;
+ buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").MinimalIndexPattern, formattedValue?: string | undefined) => import("../common").RangeFilter;
isPhraseFilter: (filter: any) => filter is import("../common").PhraseFilter;
isExistsFilter: (filter: any) => filter is import("../common").ExistsFilter;
isPhrasesFilter: (filter: any) => filter is import("../common").PhrasesFilter;
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md
index 2cde2b7455585..881a1fa803ca6 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md
@@ -10,6 +10,6 @@
esKuery: {
nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes;
fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode;
- toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject;
+ toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").MinimalIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject;
}
```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esquery.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esquery.md
index 2430e6a93bd2b..70805aaaaee8c 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esquery.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esquery.md
@@ -10,7 +10,7 @@
esQuery: {
buildEsQuery: typeof buildEsQuery;
getEsQueryConfig: typeof getEsQueryConfig;
- buildQueryFromFilters: (filters: import("../common").Filter[] | undefined, indexPattern: import("../common").IIndexPattern | undefined, ignoreFilterIfFieldNotInIndex?: boolean) => {
+ buildQueryFromFilters: (filters: import("../common").Filter[] | undefined, indexPattern: import("../common").MinimalIndexPattern | undefined, ignoreFilterIfFieldNotInIndex?: boolean) => {
must: never[];
filter: import("../common").Filter[];
should: never[];
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fields.md
deleted file mode 100644
index 792bee44f96a8..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fields.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) > [fields](./kibana-plugin-plugins-data-public.iindexpattern.fields.md)
-
-## IIndexPattern.fields property
-
-Signature:
-
-```typescript
-fields: IFieldType[];
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.id.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.id.md
deleted file mode 100644
index 917a80975df6c..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.id.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) > [id](./kibana-plugin-plugins-data-public.iindexpattern.id.md)
-
-## IIndexPattern.id property
-
-Signature:
-
-```typescript
-id?: string;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md
index bf7f88ab37039..88d8520a373c6 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md
@@ -12,7 +12,7 @@
Signature:
```typescript
-export interface IIndexPattern
+export interface IIndexPattern extends MinimalIndexPattern
```
## Properties
@@ -20,9 +20,7 @@ export interface IIndexPattern
| Property | Type | Description |
| --- | --- | --- |
| [fieldFormatMap](./kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md) | Record<string, SerializedFieldFormat<unknown> | undefined> | |
-| [fields](./kibana-plugin-plugins-data-public.iindexpattern.fields.md) | IFieldType[] | |
| [getFormatterForField](./kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md) | (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat | Look up a formatter for a given field |
-| [id](./kibana-plugin-plugins-data-public.iindexpattern.id.md) | string | |
| [timeFieldName](./kibana-plugin-plugins-data-public.iindexpattern.timefieldname.md) | string | |
| [title](./kibana-plugin-plugins-data-public.iindexpattern.title.md) | string | |
| [type](./kibana-plugin-plugins-data-public.iindexpattern.type.md) | string | Type is used for identifying rollup indices, otherwise left undefined |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md
index d7e80d94db4e6..d951cb2426943 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md
@@ -11,11 +11,11 @@ esFilters: {
buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter;
buildCustomFilter: typeof buildCustomFilter;
buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter;
- buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IIndexPattern) => import("../common").ExistsFilter;
+ buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").MinimalIndexPattern) => import("../common").ExistsFilter;
buildFilter: typeof buildFilter;
- buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IIndexPattern) => import("../common").PhraseFilter;
- buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IIndexPattern) => import("../common").PhrasesFilter;
- buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IIndexPattern, formattedValue?: string | undefined) => import("../common").RangeFilter;
+ buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").MinimalIndexPattern) => import("../common").PhraseFilter;
+ buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").MinimalIndexPattern) => import("../common").PhrasesFilter;
+ buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").MinimalIndexPattern, formattedValue?: string | undefined) => import("../common").RangeFilter;
isFilterDisabled: (filter: import("../common").Filter) => boolean;
}
```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md
index 4b96d8af756f3..6274eb5f4f4a5 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md
@@ -10,6 +10,6 @@
esKuery: {
nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes;
fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode;
- toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject;
+ toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").MinimalIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject;
}
```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esquery.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esquery.md
index ac9be23bc6b6f..0d1baecb014f5 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esquery.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esquery.md
@@ -8,7 +8,7 @@
```typescript
esQuery: {
- buildQueryFromFilters: (filters: import("../common").Filter[] | undefined, indexPattern: import("../common").IIndexPattern | undefined, ignoreFilterIfFieldNotInIndex?: boolean) => {
+ buildQueryFromFilters: (filters: import("../common").Filter[] | undefined, indexPattern: import("../common").MinimalIndexPattern | undefined, ignoreFilterIfFieldNotInIndex?: boolean) => {
must: never[];
filter: import("../common").Filter[];
should: never[];
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md
index b875b1fce4288..444132024596e 100644
--- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md
@@ -36,6 +36,7 @@
| [isSavedObjectEmbeddableInput(input)](./kibana-plugin-plugins-embeddable-public.issavedobjectembeddableinput.md) | |
| [openAddPanelFlyout(options)](./kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md) | |
| [plugin(initializerContext)](./kibana-plugin-plugins-embeddable-public.plugin.md) | |
+| [useEmbeddableFactory({ input, factory, onInputUpdated, })](./kibana-plugin-plugins-embeddable-public.useembeddablefactory.md) | |
## Interfaces
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md
new file mode 100644
index 0000000000000..9af20cacc2cee
--- /dev/null
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [useEmbeddableFactory](./kibana-plugin-plugins-embeddable-public.useembeddablefactory.md)
+
+## useEmbeddableFactory() function
+
+Signature:
+
+```typescript
+export declare function useEmbeddableFactory({ input, factory, onInputUpdated, }: EmbeddableRendererWithFactory): readonly [ErrorEmbeddable | IEmbeddable | undefined, boolean, string | undefined];
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| { input, factory, onInputUpdated, } | EmbeddableRendererWithFactory<I> | |
+
+Returns:
+
+`readonly [ErrorEmbeddable | IEmbeddable | undefined, boolean, string | undefined]`
+
diff --git a/docs/management/action-types.asciidoc b/docs/management/action-types.asciidoc
index 65b600d4b7281..3d3d7aeb2d777 100644
--- a/docs/management/action-types.asciidoc
+++ b/docs/management/action-types.asciidoc
@@ -43,6 +43,10 @@ a| <>
| Send a message to a Slack channel or user.
+a| <>
+
+| Create an incident in Swimlane.
+
a| <>
| Send a request to a web service.
diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc
index 853180ec816e9..66a23ee189ae1 100644
--- a/docs/management/advanced-options.asciidoc
+++ b/docs/management/advanced-options.asciidoc
@@ -482,6 +482,9 @@ of buckets to try to represent.
[[visualization-visualize-chartslibrary]]`visualization:visualize:legacyChartsLibrary`::
Enables the legacy charts library for aggregation-based area, line, and bar charts in *Visualize*.
+[[visualization-visualize-pieChartslibrary]]`visualization:visualize:legacyPieChartsLibrary`::
+Enables the legacy charts library for aggregation-based pie charts in *Visualize*.
+
[[visualization-colormapping]]`visualization:colorMapping`::
**This setting is deprecated and will not be supported as of 8.0.**
Maps values to specific colors in charts using the *Compatibility* palette.
diff --git a/docs/management/connectors/action-types/swimlane.asciidoc b/docs/management/connectors/action-types/swimlane.asciidoc
new file mode 100644
index 0000000000000..88447bb496a86
--- /dev/null
+++ b/docs/management/connectors/action-types/swimlane.asciidoc
@@ -0,0 +1,105 @@
+[role="xpack"]
+[[swimlane-action-type]]
+=== Swimlane connector and action
+++++
+Swimlane
+++++
+
+The Swimlane connector uses the https://swimlane.com/knowledge-center/docs/developer-guide/rest-api/[Swimlane REST API] to create Swimlane records.
+
+[float]
+[[swimlane-connector-configuration]]
+==== Connector configuration
+
+Swimlane connectors have the following configuration properties.
+
+Name:: The name of the connector. The name is used to identify a connector in the **Stack Management** UI connector listing, and in the connector list when configuring an action.
+URL:: Swimlane instance URL.
+Application ID:: Swimlane application ID.
+API token:: Swimlane API authentication token for HTTP Basic authentication.
+
+[float]
+[[Preconfigured-swimlane-configuration]]
+==== Preconfigured connector type
+
+[source,text]
+--
+ my-swimlane:
+ name: preconfigured-swimlane-connector-type
+ actionTypeId: .swimlane
+ config:
+ apiUrl: https://elastic.swimlaneurl.us
+ appId: app-id
+ mappings:
+ alertIdConfig:
+ fieldType: text
+ id: agp4s
+ key: alert-id
+ name: Alert ID
+ caseIdConfig:
+ fieldType: text
+ id: ae1mi
+ key: case-id
+ name: Case ID
+ caseNameConfig:
+ fieldType: text
+ id: anxnr
+ key: case-name
+ name: Case Name
+ commentsConfig:
+ fieldType: comments
+ id: au18d
+ key: comments
+ name: Comments
+ descriptionConfig:
+ fieldType: text
+ id: ae1gd
+ key: description
+ name: Description
+ ruleNameConfig:
+ fieldType: text
+ id: avfsl
+ key: rule-name
+ name: Rule Name
+ severityConfig:
+ fieldType: text
+ id: a71ik
+ key: severity
+ name: severity
+ secrets:
+ apiToken: tokenkeystorevalue
+--
+
+Config defines information for the connector type.
+
+`apiUrl`:: An address that corresponds to *URL*.
+`appId`:: A key that corresponds to *Application ID*.
+
+Secrets defines sensitive information for the connector type.
+
+`apiToken`:: A string that corresponds to *API Token*. Should be stored in the <>.
+
+[float]
+[[define-swimlane-ui]]
+==== Define connector in Stack Management
+
+Define Swimlane connector properties.
+
+[role="screenshot"]
+image::management/connectors/images/swimlane-connector.png[Swimlane connector]
+
+Test Swimlane action parameters.
+
+[role="screenshot"]
+image::management/connectors/images/swimlane-params-test.png[Swimlane params test]
+
+[float]
+[[swimlane-action-configuration]]
+==== Action configuration
+
+Swimlane actions have the following configuration properties.
+
+Comments:: Additional information for the client, such as how to troubleshoot the issue.
+Severity:: The severity of the incident.
+
+NOTE: Alert ID and Rule Name are filled automatically. Specifically, Alert ID is set to `{{alert.id}}` and Rule Name to `{{rule.name}}`.
\ No newline at end of file
diff --git a/docs/management/connectors/images/swimlane-connector.png b/docs/management/connectors/images/swimlane-connector.png
new file mode 100644
index 0000000000000..520c35d00381b
Binary files /dev/null and b/docs/management/connectors/images/swimlane-connector.png differ
diff --git a/docs/management/connectors/images/swimlane-params-test.png b/docs/management/connectors/images/swimlane-params-test.png
new file mode 100644
index 0000000000000..c0e02c2c7b18f
Binary files /dev/null and b/docs/management/connectors/images/swimlane-params-test.png differ
diff --git a/docs/management/connectors/index.asciidoc b/docs/management/connectors/index.asciidoc
index ea4fa46d3e808..033b1c3ac150e 100644
--- a/docs/management/connectors/index.asciidoc
+++ b/docs/management/connectors/index.asciidoc
@@ -6,6 +6,7 @@ include::action-types/teams.asciidoc[]
include::action-types/pagerduty.asciidoc[]
include::action-types/server-log.asciidoc[]
include::action-types/servicenow.asciidoc[]
+include::action-types/swimlane.asciidoc[]
include::action-types/slack.asciidoc[]
include::action-types/webhook.asciidoc[]
include::pre-configured-connectors.asciidoc[]
diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc
index 71f141d1ed5d6..d1d283ca60fbb 100644
--- a/docs/settings/alert-action-settings.asciidoc
+++ b/docs/settings/alert-action-settings.asciidoc
@@ -69,7 +69,7 @@ You can configure the following settings in the `kibana.yml` file.
--
xpack.actions.customHostSettings:
- url: smtp://mail.example.com:465
- tls:
+ ssl:
verificationMode: 'full'
certificateAuthoritiesFiles: [ 'one.crt' ]
certificateAuthoritiesData: |
@@ -79,7 +79,7 @@ xpack.actions.customHostSettings:
smtp:
requireTLS: true
- url: https://webhook.example.com
- tls:
+ ssl:
// legacy
rejectUnauthorized: false
verificationMode: 'none'
@@ -97,8 +97,8 @@ xpack.actions.customHostSettings:
server, and the `https` URLs are used for actions which use `https` to
connect to services. +
+
- Entries with `https` URLs can use the `tls` options, and entries with `smtp`
- URLs can use both the `tls` and `smtp` options. +
+ Entries with `https` URLs can use the `ssl` options, and entries with `smtp`
+ URLs can use both the `ssl` and `smtp` options. +
+
No other URL values should be part of this URL, including paths,
query strings, and authentication information. When an http or smtp request
@@ -117,24 +117,24 @@ xpack.actions.customHostSettings:
The options `smtp.ignoreTLS` and `smtp.requireTLS` can not both be set to true.
| `xpack.actions.customHostSettings[n]`
-`.tls.rejectUnauthorized` {ess-icon}
- | Deprecated. Use <> instead. A boolean value indicating whether to bypass server certificate validation.
+`.ssl.rejectUnauthorized` {ess-icon}
+ | Deprecated. Use <> instead. A boolean value indicating whether to bypass server certificate validation.
Overrides the general `xpack.actions.rejectUnauthorized` configuration
for requests made for this hostname/port.
|[[action-config-custom-host-verification-mode]] `xpack.actions.customHostSettings[n]`
-`.tls.verificationMode`
+`.ssl.verificationMode`
| Controls the verification of the server certificate that {hosted-ems} receives when making an outbound SSL/TLS connection to the host server. Valid values are `full`, `certificate`, and `none`.
- Use `full` to perform hostname verification, `certificate` to skip hostname verification, and `none` to skip verification. Default: `full`. <>. Overrides the general `xpack.actions.tls.verificationMode` configuration
+ Use `full` to perform hostname verification, `certificate` to skip hostname verification, and `none` to skip verification. Default: `full`. <>. Overrides the general `xpack.actions.ssl.verificationMode` configuration
for requests made for this hostname/port.
| `xpack.actions.customHostSettings[n]`
-`.tls.certificateAuthoritiesFiles`
+`.ssl.certificateAuthoritiesFiles`
| A file name or list of file names of PEM-encoded certificate files to use
to validate the server.
| `xpack.actions.customHostSettings[n]`
-`.tls.certificateAuthoritiesData` {ess-icon}
+`.ssl.certificateAuthoritiesData` {ess-icon}
| The contents of a PEM-encoded certificate file, or multiple files appended
into a single string. This configuration can be used for environments where
the files cannot be made available.
@@ -165,28 +165,28 @@ xpack.actions.customHostSettings:
a|`xpack.actions.`
`proxyRejectUnauthorizedCertificates` {ess-icon}
- | Deprecated. Use <> instead. Set to `false` to bypass certificate validation for the proxy, if using a proxy for actions. Default: `true`.
+ | Deprecated. Use <> instead. Set to `false` to bypass certificate validation for the proxy, if using a proxy for actions. Default: `true`.
|[[action-config-proxy-verification-mode]]
`xpack.actions[n]`
-`.tls.proxyVerificationMode` {ess-icon}
+`.ssl.proxyVerificationMode` {ess-icon}
| Controls the verification for the proxy server certificate that {hosted-ems} receives when making an outbound SSL/TLS connection to the proxy server. Valid values are `full`, `certificate`, and `none`.
Use `full` to perform hostname verification, `certificate` to skip hostname verification, and `none` to skip verification. Default: `full`. <>.
| `xpack.actions.rejectUnauthorized` {ess-icon}
- | Deprecated. Use <> instead. Set to `false` to bypass certificate validation for actions. Default: `true`. +
+ | Deprecated. Use <> instead. Set to `false` to bypass certificate validation for actions. Default: `true`. +
+
As an alternative to setting `xpack.actions.rejectUnauthorized`, you can use the setting
- `xpack.actions.customHostSettings` to set TLS options for specific servers.
+ `xpack.actions.customHostSettings` to set SSL options for specific servers.
|[[action-config-verification-mode]]
`xpack.actions[n]`
-`.tls.verificationMode` {ess-icon}
+`.ssl.verificationMode` {ess-icon}
| Controls the verification for the server certificate that {hosted-ems} receives when making an outbound SSL/TLS connection for actions. Valid values are `full`, `certificate`, and `none`.
Use `full` to perform hostname verification, `certificate` to skip hostname verification, and `none` to skip verification. Default: `full`. <>. +
+
- As an alternative to setting `xpack.actions.tls.verificationMode`, you can use the setting
- `xpack.actions.customHostSettings` to set TLS options for specific servers.
+ As an alternative to setting `xpack.actions.ssl.verificationMode`, you can use the setting
+ `xpack.actions.customHostSettings` to set SSL options for specific servers.
diff --git a/docs/settings/task-manager-settings.asciidoc b/docs/settings/task-manager-settings.asciidoc
index 87f5b700870eb..7f4dbb3a96e6b 100644
--- a/docs/settings/task-manager-settings.asciidoc
+++ b/docs/settings/task-manager-settings.asciidoc
@@ -29,7 +29,13 @@ Task Manager runs background tasks by polling for work on an interval. You can
| The maximum number of tasks that this Kibana instance will run simultaneously. Defaults to 10.
Starting in 8.0, it will not be possible to set the value greater than 100.
- | `xpack.task_manager.monitored_stats_warn_delayed_task_start_in_seconds`
+ | `xpack.task_manager.`
+ `monitored_stats_health_verbose_log.enabled`
+ | This flag will enable automatic warn and error logging if task manager self detects a performance issue, such as the time between when a task is scheduled to execute and when it actually executes. Defaults to false.
+
+ | `xpack.task_manager.`
+ `monitored_stats_health_verbose_log.`
+ `warn_delayed_task_start_in_seconds`
| The amount of seconds we allow a task to delay before printing a warning server log. Defaults to 60.
|===
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index c3c29adcea18f..bcaa86d73adc4 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -36,11 +36,57 @@ Set to `false` to disable Console. *Default: `true`*
<>.
| `csp.rules:`
- | A https://w3c.github.io/webappsec-csp/[content-security-policy] template
+ | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."]
+A https://w3c.github.io/webappsec-csp/[Content Security Policy] template
that disables certain unnecessary and potentially insecure capabilities in
the browser. It is strongly recommended that you keep the default CSP rules
that ship with {kib}.
+| `csp.script_src:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src[Content Security Policy `script-src` directive].
+
+| `csp.worker_src:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src[Content Security Policy `worker-src` directive].
+
+| `csp.style_src:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src[Content Security Policy `style-src` directive].
+
+| `csp.connect_src:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src[Content Security Policy `connect-src` directive].
+
+| `csp.default_src:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src[Content Security Policy `default-src` directive].
+
+| `csp.font_src:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/font-src[Content Security Policy `font-src` directive].
+
+| `csp.frame_src:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src[Content Security Policy `frame-src` directive].
+
+| `csp.img_src:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src[Content Security Policy `img-src` directive].
+
+| `csp.frame_ancestors:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors[Content Security Policy `frame-ancestors` directive].
+
+|===
+
+[NOTE]
+============
+The `frame-ancestors` directive can also be configured by using
+<>. In that case, that takes precedence and any values in `csp.frame_ancestors`
+are ignored.
+============
+
+[cols="2*<"]
+|===
+
+| `csp.report_uri:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri[Content Security Policy `report-uri` directive].
+
+| `csp.report_to:`
+| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to[Content Security Policy `report-to` directive].
+
|[[csp-strict]] `csp.strict:`
| Blocks {kib} access to any browser that
does not enforce even rudimentary CSP rules. In practice, this disables
@@ -538,8 +584,7 @@ a|`server.securityResponseHeaders:`
is used in all responses to the client from the {kib} server, and specifies what value is used. Allowed values are any text value or `null`.
To disable, set to `null`. *Default:* `null`
-[[server-securityResponseHeaders-disableEmbedding]]
-a|`server.securityResponseHeaders:`
+|[[server-securityResponseHeaders-disableEmbedding]]`server.securityResponseHeaders:`
`disableEmbedding:`
| Controls whether the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy[`Content-Security-Policy`] and
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options[`X-Frame-Options`] headers are configured to disable embedding
diff --git a/docs/setup/upgrade/upgrade-migrations.asciidoc b/docs/setup/upgrade/upgrade-migrations.asciidoc
index fdcd71791ad3a..947043b21ef50 100644
--- a/docs/setup/upgrade/upgrade-migrations.asciidoc
+++ b/docs/setup/upgrade/upgrade-migrations.asciidoc
@@ -55,22 +55,55 @@ This section highlights common causes of {kib} upgrade failures and how to preve
There is a known issue in v7.12.0 for users who tried the fleet beta. Upgrade migrations fail because of a large number of documents in the `.kibana` index.
This can cause Kibana to log errors like:
-> Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [receive_timeout_transport_exception]: [instance-0000000002][10.32.1.112:19541][cluster:monitor/task/get] request_id [2648] timed out after [59940ms]
-> Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [timeout_exception]: Timed out waiting for completion of [org.elasticsearch.index.reindex.BulkByScrollTask@6a74c54]
+
+[source,sh]
+--------------------------------------------
+Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [receive_timeout_transport_exception]: [instance-0000000002][10.32.1.112:19541][cluster:monitor/task/get] request_id [2648] timed out after [59940ms]
+
+Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [timeout_exception]: Timed out waiting for completion of [org.elasticsearch.index.reindex.BulkByScrollTask@6a74c54]
+--------------------------------------------
See https://github.com/elastic/kibana/issues/95321 for instructions to work around this issue.
[float]
===== Corrupt saved objects
-We highly recommend testing your {kib} upgrade in a development cluster to discover and remedy problems caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. Saved objects that were corrupted through manual editing or integrations will cause migration failures with a log message like `Failed to transform document. Transform: index-pattern:7.0.0\n Doc: {...}` or `Unable to migrate the corrupt Saved Object document ...`. Corrupt documents will have to be fixed or deleted before an upgrade migration can succeed.
+We highly recommend testing your {kib} upgrade in a development cluster to discover and remedy problems caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment.
+
+Saved objects that were corrupted through manual editing or integrations will cause migration failures with a log message like `Failed to transform document. Transform: index-pattern:7.0.0\n Doc: {...}` or `Unable to migrate the corrupt Saved Object document ...`. Corrupt documents will have to be fixed or deleted before an upgrade migration can succeed.
For example, given the following error message:
-> Unable to migrate the corrupt saved object document with _id: 'marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275'. To allow migrations to proceed, please delete this document from the [.kibana_7.12.0_001] index.
-The following steps must be followed to allow the upgrade migration to succeed.
-Please be aware the Dashboard having ID `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` belonging to the space `marketing_space` will no more be available:
-1. Delete the corrupt document with `DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275`
-2. Restart {kib}
+[source,sh]
+--------------------------------------------
+Unable to migrate the corrupt saved object document with _id: 'marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275'. To allow migrations to proceed, please delete this document from the [.kibana_7.12.0_001] index.
+--------------------------------------------
+
+The following steps must be followed to delete the document that is causing the migration to fail:
+
+. Remove the write block which the migration system has placed on the previous index:
++
+[source,sh]
+--------------------------------------------
+PUT .kibana_7.12.1_001/_settings
+{
+ "index": {
+ "blocks.write": false
+ }
+}
+--------------------------------------------
+
+. Delete the corrupt document:
++
+[source,sh]
+--------------------------------------------
+DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275
+--------------------------------------------
+
+. Restart {kib}.
+
+In this example, the Dashboard with ID `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` that belongs to the space `marketing_space` **will no longer be available**.
+
+Be sure you have a snapshot before you delete the corrupt document. If restoring from a snapshot is not an option, it is recommended to also delete the `temp` and `target` indices the migration created before restarting {kib} and retrying.
[float]
===== User defined index templates that causes new `.kibana*` indices to have incompatible settings or mappings
diff --git a/docs/siem/index.asciidoc b/docs/siem/index.asciidoc
index 18895f0533fd7..05b1ec0b5b797 100644
--- a/docs/siem/index.asciidoc
+++ b/docs/siem/index.asciidoc
@@ -1,60 +1,164 @@
+[chapter]
[role="xpack"]
[[xpack-siem]]
-= Elastic Security
+= Elastic Security overview
+++++
+Security
+++++
-[partintro]
---
+https://www.elastic.co/security[Elastic Security] combines SIEM threat detection features with endpoint
+prevention and response capabilities in one solution. These analytical and
+protection capabilities, leveraged by the speed and extensibility of
+Elasticsearch, enable analysts to defend their organization from threats before
+damage and loss occur.
-Elastic Security combines SIEM threat detection features with endpoint
-prevention and response capabilities in one solution, including:
+Elastic Security provides the following security benefits and capabilities:
-* A detection engine to identify attacks and system misconfiguration
+* A detection engine to identify attacks and system misconfigurations
* A workspace for event triage and investigations
* Interactive visualizations to investigate process relationships
-* Embedded case management and automated actions
-* Detection of signatureless attacks with prebuilt {ml} anomaly jobs and
-detection rules
+* Inbuilt case management with automated actions
+* Detection of signatureless attacks with prebuilt machine learning anomaly jobs
+and detection rules
-[role="screenshot"]
-image::siem/images/overview-ui.png[Elastic Security in Kibana]
-
-[float]
-== Add data
-
-Kibana provides step-by-step instructions to help you add data. The
-{security-guide}[Security Guide] is a good source for more
-detailed information and instructions.
-
-[float]
-=== {Beats}
-
-https://www.elastic.co/products/beats/auditbeat[{auditbeat}],
-https://www.elastic.co/products/beats/filebeat[{filebeat}],
-https://www.elastic.co/products/beats/winlogbeat[{winlogbeat}], and
-https://www.elastic.co/products/beats/packetbeat[{packetbeat}]
-send security events and other data to Elasticsearch.
+[discrete]
+== Elastic Security components and workflow
-The default index patterns for Elastic Security events are `auditbeat-*`, `winlogbeat-*`,
-`filebeat-*`, `packetbeat-*`, `endgame-*`, `logs-*`, and `apm-*-transaction*`. To change the default pattern patterns, go to *Stack Management > Advanced Settings > securitySolution:defaultIndex*.
+The following diagram provides a comprehensive illustration of the Elastic Security workflow.
-[float]
-=== Elastic Security endpoint agent
-
-The agent detects and protects against malware, and ships host and network
-events directly to Elastic Security.
-
-[float]
-=== Elastic Common Schema (ECS) for normalizing data
-
-The {ecs-ref}[Elastic Common Schema (ECS)] defines a common set of fields to be
-used for storing event data in Elasticsearch. ECS helps users normalize their
-event data to better analyze, visualize, and correlate the data represented in
-their events.
-
-Elastic Security can ingest and normalize events from ECS-compatible data sources.
+[role="screenshot"]
+image::../siem/images/workflow.png[]
+
+Here's an overview of the flow and its components:
+
+* Data is shipped from your hosts to {es} via beat modules and the Elastic https://www.elastic.co/endpoint-security/[Endpoint Security agent integration]. This integration provides capabilities such as collecting events, detecting and preventing {security-guide}/detection-engine-overview.html#malware-prevention[malicious activity], and artifact delivery. The {fleet-guide}/fleet-overview.html[{fleet}] app is used to
+install and manage agents and integrations on your hosts.
++
+The Endpoint Security integration ships the following data sets:
++
+*** *Windows*: Process, network, file, DNS, registry, DLL and driver loads,
+malware security detections
+*** *Linux/macOS*: Process, network, file
++
+* https://www.elastic.co/integrations?solution=security[Beat modules]: {beats}
+are lightweight data shippers. Beat modules provide a way of collecting and
+parsing specific data sets from common sources, such as cloud and OS events,
+logs, and metrics. Common security-related modules are listed {security-guide}/ingest-data.html#enable-beat-modules[here].
+* The {security-app} in {kib} is used to manage the *Detection engine*,
+*Cases*, and *Timeline*, as well as administer hosts running Endpoint Security:
+** Detection engine: Automatically searches for suspicious host and network
+activity via the following:
+*** {security-guide}/detection-engine-overview.html#detection-engine-overview[Detection rules]: Periodically search the data
+({es} indices) sent from your hosts for suspicious events. When a suspicious
+event is discovered, a detection alert is generated. External systems, such as
+Slack and email, can be used to send notifications when alerts are generated.
+You can create your own rules and make use of our {security-guide}/prebuilt-rules.html[prebuilt ones].
+*** {security-guide}/detections-ui-exceptions.html[Exceptions]: Reduce noise and the number of
+false positives. Exceptions are associated with rules and prevent alerts when
+an exception's conditions are met. *Value lists* contain source event
+values that can be used as part of an exception's conditions. When
+Elastic {endpoint-sec} is installed on your hosts, you can add malware exceptions
+directly to the endpoint from the Security app.
+*** {security-guide}/machine-learning.html#included-jobs[{ml-cap} jobs]: Automatic anomaly detection of host and
+network events. Anomaly scores are provided per host and can be used with
+detection rules.
+** {security-guide}/timelines-ui.html[Timeline]: Workspace for investigating alerts and events.
+Timelines use queries and filters to drill down into events related to
+a specific incident. Timeline templates are attached to rules and use predefined
+queries when alerts are investigated. Timelines can be saved and shared with
+others, as well as attached to Cases.
+** {security-guide}/cases-overview.html[Cases]: An internal system for opening, tracking, and sharing
+security issues directly in the Security app. Cases can be integrated with
+external ticketing systems.
+** {security-guide}/admin-page-ov.html[Administration]: View and manage hosts running {endpoint-sec}.
+
+{security-guide}/ingest-data.html[Ingest data to Elastic Security] and {security-guide}/install-endpoint.html[Configure and install the Elastic Endpoint integration] describe how to ship security-related
+data to {es}.
+
+
+For more background information, see:
+
+* https://www.elastic.co/products/elasticsearch[{es}]: A real-time,
+distributed storage, search, and analytics engine. {es} excels at indexing
+streams of semi-structured data, such as logs or metrics.
+* https://www.elastic.co/products/kibana[{kib}]: An open-source analytics and
+visualization platform designed to work with {es}. You use {kib} to search,
+view, and interact with data stored in {es} indices. You can easily compile
+advanced data analysis and visualize your data in a variety of charts, tables,
+and maps.
+
+[discrete]
+=== Compatibility with cold tier nodes
+
+Cold tier is a {ref}/data-tiers.html[data tier] that holds time-series data that is accessed only occasionally. In {stack} version >=7.11.0, {elastic-sec} supports cold tier data for the following {es} indices:
+
+* Index patterns specified in `securitySolution:defaultIndex`
+* Index patterns specified in the definitions of detection rules, except for indicator match rules
+* Index patterns specified in the data sources selector on various {security-app} pages
+
+{elastic-sec} does NOT support cold tier data for the following {es} indices:
+
+* Index patterns controlled by {elastic-sec}, including signals and list indices
+* Index patterns specified in indicator match rules
+
+Using cold tier data for unsupported indices may result in detection rule timeouts and overall performance degradation.
+
+[discrete]
+[[self-protection]]
+==== Elastic Endpoint self-protection
+
+Self-protection means that {elastic-endpoint} has guards against users and attackers that may try to interfere with its functionality. This protection feature is consistently enhanced to prevent attackers who may attempt to use newer, more sophisticated tactics to interfere with the {elastic-endpoint}. Self-protection is enabled by default when {elastic-endpoint} installs on supported platforms, listed below.
+
+Self-protection is enabled on the following 64-bit Windows versions:
+
+* Windows 8.1
+* Windows 10
+* Windows Server 2012 R2
+* Windows Server 2016
+* Windows Server 2019
+
+And on the following macOS versions:
+
+* macOS 10.15 (Catalina)
+* macOS 11 (Big Sur)
+
+NOTE: Other Windows and macOS variants (and all Linux distributions) do not have self-protection.
+
+For {stack} version >= 7.11.0, self-protection defines the following permissions:
+
+* Users -- even Administrator/root -- *cannot* delete {elastic-endpoint} files (located at `c:\Program Files\Elastic\Endpoint` on Windows, and `/Library/Elastic/Endpoint` on macOS).
+* Users *cannot* terminate the {elastic-endpoint} program or service.
+* Administrator/root users *can* read the endpoint's files. On Windows, the easiest way to read Endpoint files is to start an Administrator `cmd.exe` prompt. On macOS, an Administrator can use the `sudo` command.
+* Administrator/root users *can* stop the {elastic-agent}'s service. On Windows, run the `sc stop "Elastic Agent"` command. On macOS, run the `sudo launchctl stop elastic-agent` command.
+
+
+[discrete]
+[[siem-integration]]
+=== Integration with other Elastic products
+
+You can use {elastic-sec} with other Elastic products and features to help you
+identify and investigate suspicious activity:
+
+* https://www.elastic.co/products/stack/machine-learning[{ml-cap}]
+* https://www.elastic.co/products/stack/alerting[Alerting]
+* https://www.elastic.co/products/stack/canvas[Canvas]
+
+[discrete]
+[[data-sources]]
+=== APM transaction data sources
+
+By default, {elastic-sec} monitors {apm-app-ref}/apm-getting-started.html[APM]
+`apm-*-transaction*` indices. To add additional APM indices, update the
+index patterns in the `securitySolution:defaultIndex` setting ({kib} -> Stack Management -> Advanced Settings -> `securitySolution:defaultIndex`).
---
+[discrete]
+[[ecs-compliant-reqs]]
+=== ECS compliance data requirements
+The {ecs-ref}[Elastic Common Schema (ECS)] defines a common set of fields used for
+storing event data in Elasticsearch. ECS helps users normalize their event data
+to better analyze, visualize, and correlate the data represented in their
+events. {elastic-sec} supports events and indicator index data from any ECS-compliant data source.
-include::siem-ui.asciidoc[]
-include::machine-learning.asciidoc[]
+IMPORTANT: {elastic-sec} requires {ecs-ref}[ECS-compliant data]. If you use third-party data collectors to ship data to {es}, the data must be mapped to ECS.
+{security-guide}/siem-field-reference.html[Elastic Security ECS field reference] lists ECS fields used in {elastic-sec}.
diff --git a/docs/user/dashboard/lens-advanced.asciidoc b/docs/user/dashboard/lens-advanced.asciidoc
index ec8d90aa4920e..33e0e362058f4 100644
--- a/docs/user/dashboard/lens-advanced.asciidoc
+++ b/docs/user/dashboard/lens-advanced.asciidoc
@@ -104,7 +104,7 @@ To quickly create many copies of a percentile metric that shows distribution of
. From the *Chart Type* dropdown, select *Line*.
+
[role="screenshot"]
-image::images/lens_advanced_2_1.png[Chart type menu with Line selected]
+image::images/lens_advanced_2_1.png[Chart type menu with Line selected, width=50%]
. From the *Available fields* list, drag and drop *products.price* to the visualization builder.
@@ -239,12 +239,11 @@ For each category type that you want to break down, create a filter.
Change the legend position to the top of the chart.
. From the *Legend* dropdown, select the top position.
-
+
[role="screenshot"]
image::images/lens_advanced_4_1.png[Prices share by category]
- Click *Save and return*.
+. Click *Save and return*.
[discrete]
[[view-the-cumulative-number-of-products-sold-on-weekends]]
@@ -299,7 +298,8 @@ image::images/lens_advanced_5_2.png[Line chart with cumulative sum of orders mad
[[compare-time-ranges]]
=== Compare time ranges
-*Lens* allows you to compare the currently selected time range with historical data using the *Time shift* option.
+*Lens* allows you to compare the currently selected time range with historical data using the *Time shift* option. To calculate the percent
+change, use *Formula*.
Time shifts can be used on any metric. The special shift *previous* will show the time window preceding the currently selected one, spanning the same duration.
For example, if *Last 7 days* is selected in the time filter, *previous* will show data from 14 days ago to 7 days ago.
@@ -326,9 +326,32 @@ To compare current sales numbers with sales from a week ago, follow these steps:
.. Click *Time shift*
.. Click the *1 week* option. You can also define custom shifts by typing amount followed by time unit (like *1w* for a one week shift), then hit enter.
-
++
[role="screenshot"]
-image::images/lens_time_shift.png[Line chart with week-over-week sales comparison]
+image::images/lens_time_shift.png[Line chart with week-over-week sales comparison, width=50%]
+
+. Click *Save and return*.
+
+[float]
+[[compare-time-as-percent]]
+==== Compare time ranges as a percent change
+
+To view the percent change in sales between the current time and the previous week, use a *Formula*:
+
+. Open *Lens*.
+
+. From the *Available fields* list, drag and drop *Records* to the visualization builder.
+
+. Click *Count of Records*, then click *Formula*.
+
+. Type `count() / count(shift='1w') - 1`. To learn more about the formula
+syntax, click *Help*.
+
+. Click *Value format* and select *Percent* with 0 decimals.
+
+. In the *Display name* field, enter `Percent change`, then click *Close*.
+
+. Click *Save and return*.
[discrete]
[[view-customers-over-time-by-continents]]
@@ -366,18 +389,14 @@ To split the customers count by continent:
. From the *Available fields* list, drag and drop *geoip.continent_name* to the *Columns* field of the editor.
+
[role="screenshot"]
-image::images/lens_advanced_6_1.png[Table with daily customers by continent configuration]
+image::images/lens_advanced_6_1.png[Table with daily customers by continent configuration, width=50%]
. Click *Save and return*.
+
[discrete]
=== Save the dashboard
-By default the dashboard attempts to match the palette across panels, but in this case there's no need for that, so it can be disabled.
-
-[role="screenshot"]
-image::images/lens_advanced_7_1.png[Disable palette sync in dashboard]
-
Now that you have a complete overview of your ecommerce sales data, save the dashboard.
. In the toolbar, click *Save*.
diff --git a/package.json b/package.json
index 26465133569cd..ceb178d068519 100644
--- a/package.json
+++ b/package.json
@@ -103,16 +103,16 @@
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.13",
"@elastic/ems-client": "7.14.0",
- "@elastic/eui": "33.0.0",
+ "@elastic/eui": "34.3.0",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "^9.0.1-kibana3",
"@elastic/maki": "6.3.0",
"@elastic/node-crypto": "1.2.1",
"@elastic/numeral": "^2.5.1",
- "@elastic/react-search-ui": "^1.5.1",
+ "@elastic/react-search-ui": "^1.6.0",
"@elastic/request-crypto": "1.1.4",
"@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set",
- "@elastic/search-ui-app-search-connector": "^1.5.0",
+ "@elastic/search-ui-app-search-connector": "^1.6.0",
"@elastic/ui-ace": "0.2.3",
"@hapi/accept": "^5.0.2",
"@hapi/boom": "^9.1.1",
@@ -128,25 +128,26 @@
"@kbn/analytics": "link:bazel-bin/packages/kbn-analytics",
"@kbn/apm-config-loader": "link:bazel-bin/packages/kbn-apm-config-loader",
"@kbn/apm-utils": "link:bazel-bin/packages/kbn-apm-utils",
+ "@kbn/common-utils": "link:bazel-bin/packages/kbn-common-utils",
"@kbn/config": "link:bazel-bin/packages/kbn-config",
"@kbn/config-schema": "link:bazel-bin/packages/kbn-config-schema",
"@kbn/crypto": "link:bazel-bin/packages/kbn-crypto",
- "@kbn/mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl",
"@kbn/i18n": "link:bazel-bin/packages/kbn-i18n",
"@kbn/interpreter": "link:bazel-bin/packages/kbn-interpreter",
"@kbn/io-ts-utils": "link:bazel-bin/packages/kbn-io-ts-utils",
"@kbn/legacy-logging": "link:bazel-bin/packages/kbn-legacy-logging",
"@kbn/logging": "link:bazel-bin/packages/kbn-logging",
+ "@kbn/mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl",
"@kbn/monaco": "link:bazel-bin/packages/kbn-monaco",
"@kbn/rule-data-utils": "link:bazel-bin/packages/kbn-rule-data-utils",
- "@kbn/securitysolution-list-constants": "link:bazel-bin/packages/kbn-securitysolution-list-constants",
"@kbn/securitysolution-es-utils": "link:bazel-bin/packages/kbn-securitysolution-es-utils",
"@kbn/securitysolution-hook-utils": "link:bazel-bin/packages/kbn-securitysolution-hook-utils",
- "@kbn/securitysolution-io-ts-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-types",
"@kbn/securitysolution-io-ts-alerting-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-alerting-types",
"@kbn/securitysolution-io-ts-list-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-list-types",
+ "@kbn/securitysolution-io-ts-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-types",
"@kbn/securitysolution-io-ts-utils": "link:bazel-bin/packages/kbn-securitysolution-io-ts-utils",
"@kbn/securitysolution-list-api": "link:bazel-bin/packages/kbn-securitysolution-list-api",
+ "@kbn/securitysolution-list-constants": "link:bazel-bin/packages/kbn-securitysolution-list-constants",
"@kbn/securitysolution-list-hooks": "link:bazel-bin/packages/kbn-securitysolution-list-hooks",
"@kbn/securitysolution-list-utils": "link:bazel-bin/packages/kbn-securitysolution-list-utils",
"@kbn/securitysolution-t-grid": "link:bazel-bin/packages/kbn-securitysolution-t-grid",
@@ -158,7 +159,6 @@
"@kbn/ui-framework": "link:bazel-bin/packages/kbn-ui-framework",
"@kbn/ui-shared-deps": "link:bazel-bin/packages/kbn-ui-shared-deps",
"@kbn/utility-types": "link:bazel-bin/packages/kbn-utility-types",
- "@kbn/common-utils": "link:bazel-bin/packages/kbn-common-utils",
"@kbn/utils": "link:bazel-bin/packages/kbn-utils",
"@loaders.gl/core": "^2.3.1",
"@loaders.gl/json": "^2.3.1",
@@ -273,6 +273,7 @@
"jquery": "^3.5.0",
"js-levenshtein": "^1.1.6",
"js-search": "^1.4.3",
+ "js-sha256": "^0.9.0",
"js-yaml": "^3.14.0",
"json-stable-stringify": "^1.0.1",
"json-stringify-pretty-compact": "1.2.0",
@@ -457,7 +458,7 @@
"@jest/reporters": "^26.6.2",
"@kbn/babel-code-parser": "link:bazel-bin/packages/kbn-babel-code-parser",
"@kbn/babel-preset": "link:bazel-bin/packages/kbn-babel-preset",
- "@kbn/cli-dev-mode": "link:packages/kbn-cli-dev-mode",
+ "@kbn/cli-dev-mode": "link:bazel-bin/packages/kbn-cli-dev-mode",
"@kbn/dev-utils": "link:bazel-bin/packages/kbn-dev-utils",
"@kbn/docs-utils": "link:bazel-bin/packages/kbn-docs-utils",
"@kbn/es": "link:bazel-bin/packages/kbn-es",
@@ -467,7 +468,7 @@
"@kbn/expect": "link:bazel-bin/packages/kbn-expect",
"@kbn/optimizer": "link:bazel-bin/packages/kbn-optimizer",
"@kbn/plugin-generator": "link:bazel-bin/packages/kbn-plugin-generator",
- "@kbn/plugin-helpers": "link:packages/kbn-plugin-helpers",
+ "@kbn/plugin-helpers": "link:bazel-bin/packages/kbn-plugin-helpers",
"@kbn/pm": "link:packages/kbn-pm",
"@kbn/storybook": "link:bazel-bin/packages/kbn-storybook",
"@kbn/telemetry-tools": "link:bazel-bin/packages/kbn-telemetry-tools",
diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel
index d9e2f0e1f9985..1094a2def3e70 100644
--- a/packages/BUILD.bazel
+++ b/packages/BUILD.bazel
@@ -12,6 +12,7 @@ filegroup(
"//packages/kbn-apm-utils:build",
"//packages/kbn-babel-code-parser:build",
"//packages/kbn-babel-preset:build",
+ "//packages/kbn-cli-dev-mode:build",
"//packages/kbn-common-utils:build",
"//packages/kbn-config:build",
"//packages/kbn-config-schema:build",
@@ -31,6 +32,7 @@ filegroup(
"//packages/kbn-monaco:build",
"//packages/kbn-optimizer:build",
"//packages/kbn-plugin-generator:build",
+ "//packages/kbn-plugin-helpers:build",
"//packages/kbn-rule-data-utils:build",
"//packages/kbn-securitysolution-list-constants:build",
"//packages/kbn-securitysolution-io-ts-types:build",
diff --git a/packages/elastic-eslint-config-kibana/.eslintrc.js b/packages/elastic-eslint-config-kibana/.eslintrc.js
index a8c2e9546510e..3220a01184004 100644
--- a/packages/elastic-eslint-config-kibana/.eslintrc.js
+++ b/packages/elastic-eslint-config-kibana/.eslintrc.js
@@ -75,6 +75,11 @@ module.exports = {
to: '@kbn/test',
disallowedMessage: `import from the root of @kbn/test instead`
},
+ {
+ from: 'react-intl',
+ to: '@kbn/i18n/react',
+ disallowedMessage: `import from @kbn/i18n/react instead`
+ }
],
],
},
diff --git a/packages/kbn-cli-dev-mode/BUILD.bazel b/packages/kbn-cli-dev-mode/BUILD.bazel
new file mode 100644
index 0000000000000..ab1b6601f429b
--- /dev/null
+++ b/packages/kbn-cli-dev-mode/BUILD.bazel
@@ -0,0 +1,103 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
+
+PKG_BASE_NAME = "kbn-cli-dev-mode"
+PKG_REQUIRE_NAME = "@kbn/cli-dev-mode"
+
+SOURCE_FILES = glob(
+ [
+ "src/**/*.ts",
+ ],
+ exclude = ["**/*.test.*"],
+)
+
+SRCS = SOURCE_FILES
+
+filegroup(
+ name = "srcs",
+ srcs = SRCS,
+)
+
+NPM_MODULE_EXTRA_FILES = [
+ "package.json",
+ "README.md"
+]
+
+SRC_DEPS = [
+ "//packages/kbn-config",
+ "//packages/kbn-config-schema",
+ "//packages/kbn-dev-utils",
+ "//packages/kbn-logging",
+ "//packages/kbn-optimizer",
+ "//packages/kbn-server-http-tools",
+ "//packages/kbn-std",
+ "//packages/kbn-utils",
+ "@npm//@hapi/h2o2",
+ "@npm//@hapi/hapi",
+ "@npm//argsplit",
+ "@npm//chokidar",
+ "@npm//elastic-apm-node",
+ "@npm//execa",
+ "@npm//getopts",
+ "@npm//lodash",
+ "@npm//moment",
+ "@npm//rxjs",
+ "@npm//supertest",
+]
+
+TYPES_DEPS = [
+ "@npm//@types/hapi__h2o2",
+ "@npm//@types/hapi__hapi",
+ "@npm//@types/getopts",
+ "@npm//@types/jest",
+ "@npm//@types/lodash",
+ "@npm//@types/node",
+ "@npm//@types/supertest",
+]
+
+DEPS = SRC_DEPS + TYPES_DEPS
+
+ts_config(
+ name = "tsconfig",
+ src = "tsconfig.json",
+ deps = [
+ "//:tsconfig.base.json",
+ ],
+)
+
+ts_project(
+ name = "tsc",
+ args = ['--pretty'],
+ srcs = SRCS,
+ deps = DEPS,
+ declaration = True,
+ declaration_map = True,
+ incremental = True,
+ out_dir = "target",
+ source_map = True,
+ root_dir = "src",
+ tsconfig = ":tsconfig",
+)
+
+js_library(
+ name = PKG_BASE_NAME,
+ srcs = NPM_MODULE_EXTRA_FILES,
+ deps = DEPS + [":tsc"],
+ package_name = PKG_REQUIRE_NAME,
+ visibility = ["//visibility:public"],
+)
+
+pkg_npm(
+ name = "npm_module",
+ deps = [
+ ":%s" % PKG_BASE_NAME,
+ ]
+)
+
+filegroup(
+ name = "build",
+ srcs = [
+ ":npm_module",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/packages/kbn-cli-dev-mode/package.json b/packages/kbn-cli-dev-mode/package.json
index cf6fcfd88a26d..ac86ee2ef369b 100644
--- a/packages/kbn-cli-dev-mode/package.json
+++ b/packages/kbn-cli-dev-mode/package.json
@@ -5,11 +5,6 @@
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0",
"private": true,
- "scripts": {
- "build": "../../node_modules/.bin/tsc",
- "kbn:bootstrap": "yarn build",
- "kbn:watch": "yarn build --watch"
- },
"kibana": {
"devOnly": true
}
diff --git a/packages/kbn-cli-dev-mode/tsconfig.json b/packages/kbn-cli-dev-mode/tsconfig.json
index 4436d27dbff88..0c71ad8e245d4 100644
--- a/packages/kbn-cli-dev-mode/tsconfig.json
+++ b/packages/kbn-cli-dev-mode/tsconfig.json
@@ -1,10 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
- "incremental": false,
+ "incremental": true,
"outDir": "./target",
"declaration": true,
"declarationMap": true,
+ "rootDir": "./src",
"sourceMap": true,
"sourceRoot": "../../../../packages/kbn-cli-dev-mode/src",
"types": [
diff --git a/packages/kbn-i18n/src/react/index.tsx b/packages/kbn-i18n/src/react/index.tsx
index 08fa7173978d9..bc0a164d412af 100644
--- a/packages/kbn-i18n/src/react/index.tsx
+++ b/packages/kbn-i18n/src/react/index.tsx
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+// eslint-disable-next-line @kbn/eslint/module_migration
import { InjectedIntl as _InjectedIntl, InjectedIntlProps as _InjectedIntlProps } from 'react-intl';
export type { InjectedIntl, InjectedIntlProps } from 'react-intl';
diff --git a/packages/kbn-i18n/src/react/provider.tsx b/packages/kbn-i18n/src/react/provider.tsx
index 2d88125291aa0..fc0f6769c7160 100644
--- a/packages/kbn-i18n/src/react/provider.tsx
+++ b/packages/kbn-i18n/src/react/provider.tsx
@@ -8,6 +8,8 @@
import * as PropTypes from 'prop-types';
import * as React from 'react';
+
+// eslint-disable-next-line @kbn/eslint/module_migration
import { IntlProvider } from 'react-intl';
import * as i18n from '../core';
diff --git a/packages/kbn-interpreter/BUILD.bazel b/packages/kbn-interpreter/BUILD.bazel
index 4492faabfdf81..c29faf65638ca 100644
--- a/packages/kbn-interpreter/BUILD.bazel
+++ b/packages/kbn-interpreter/BUILD.bazel
@@ -1,5 +1,5 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
-load("@npm//pegjs:index.bzl", "pegjs")
+load("@npm//peggy:index.bzl", "peggy")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
PKG_BASE_NAME = "kbn-interpreter"
@@ -37,10 +37,10 @@ TYPES_DEPS = [
DEPS = SRC_DEPS + TYPES_DEPS
-pegjs(
+peggy(
name = "grammar",
data = [
- ":grammar/grammar.pegjs"
+ ":grammar/grammar.peggy"
],
output_dir = True,
args = [
@@ -48,7 +48,7 @@ pegjs(
"expression,argument",
"-o",
"$(@D)/index.js",
- "./%s/grammar/grammar.pegjs" % package_name()
+ "./%s/grammar/grammar.peggy" % package_name()
],
)
diff --git a/packages/kbn-interpreter/grammar/grammar.pegjs b/packages/kbn-interpreter/grammar/grammar.peggy
similarity index 100%
rename from packages/kbn-interpreter/grammar/grammar.pegjs
rename to packages/kbn-interpreter/grammar/grammar.peggy
diff --git a/packages/kbn-plugin-helpers/BUILD.bazel b/packages/kbn-plugin-helpers/BUILD.bazel
new file mode 100644
index 0000000000000..1a1f3453f768a
--- /dev/null
+++ b/packages/kbn-plugin-helpers/BUILD.bazel
@@ -0,0 +1,97 @@
+
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
+
+PKG_BASE_NAME = "kbn-plugin-helpers"
+PKG_REQUIRE_NAME = "@kbn/plugin-helpers"
+
+SOURCE_FILES = glob(
+ [
+ "src/**/*.ts",
+ ],
+ exclude = [
+ "**/*.test.*",
+ ],
+)
+
+SRCS = SOURCE_FILES
+
+filegroup(
+ name = "srcs",
+ srcs = SRCS,
+)
+
+NPM_MODULE_EXTRA_FILES = [
+ "package.json",
+ "README.md"
+]
+
+SRC_DEPS = [
+ "//packages/kbn-dev-utils",
+ "//packages/kbn-optimizer",
+ "//packages/kbn-utils",
+ "@npm//del",
+ "@npm//execa",
+ "@npm//extract-zip",
+ "@npm//globby",
+ "@npm//gulp-zip",
+ "@npm//inquirer",
+ "@npm//load-json-file",
+ "@npm//vinyl-fs",
+]
+
+TYPES_DEPS = [
+ "@npm//@types/extract-zip",
+ "@npm//@types/gulp-zip",
+ "@npm//@types/inquirer",
+ "@npm//@types/jest",
+ "@npm//@types/node",
+ "@npm//@types/vinyl-fs",
+]
+
+DEPS = SRC_DEPS + TYPES_DEPS
+
+ts_config(
+ name = "tsconfig",
+ src = "tsconfig.json",
+ deps = [
+ "//:tsconfig.base.json",
+ ],
+)
+
+ts_project(
+ name = "tsc",
+ args = ['--pretty'],
+ srcs = SRCS,
+ deps = DEPS,
+ declaration = True,
+ declaration_map = True,
+ incremental = True,
+ out_dir = "target",
+ source_map = True,
+ root_dir = "src",
+ tsconfig = ":tsconfig",
+)
+
+js_library(
+ name = PKG_BASE_NAME,
+ srcs = NPM_MODULE_EXTRA_FILES,
+ deps = DEPS + [":tsc"],
+ package_name = PKG_REQUIRE_NAME,
+ visibility = ["//visibility:public"],
+)
+
+pkg_npm(
+ name = "npm_module",
+ deps = [
+ ":%s" % PKG_BASE_NAME,
+ ]
+)
+
+filegroup(
+ name = "build",
+ srcs = [
+ ":npm_module",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json
index 36a37075191a3..1f4df52a03304 100644
--- a/packages/kbn-plugin-helpers/package.json
+++ b/packages/kbn-plugin-helpers/package.json
@@ -11,9 +11,5 @@
"types": "target/index.d.ts",
"bin": {
"plugin-helpers": "bin/plugin-helpers.js"
- },
- "scripts": {
- "kbn:bootstrap": "rm -rf target && ../../node_modules/.bin/tsc",
- "kbn:watch": "../../node_modules/.bin/tsc --watch"
}
}
\ No newline at end of file
diff --git a/packages/kbn-plugin-helpers/tsconfig.json b/packages/kbn-plugin-helpers/tsconfig.json
index 87d11843f398a..4348f1e1a7516 100644
--- a/packages/kbn-plugin-helpers/tsconfig.json
+++ b/packages/kbn-plugin-helpers/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
- "incremental": false,
+ "incremental": true,
"outDir": "target",
"target": "ES2018",
"declaration": true,
diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js
index e455f487d1384..5be9dff630ed5 100644
--- a/packages/kbn-pm/dist/index.js
+++ b/packages/kbn-pm/dist/index.js
@@ -63827,6 +63827,7 @@ function getProjectPaths({
projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*'));
projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*'));
+ projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/server_integration/__fixtures__/plugins/*'));
projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'examples/*'));
if (!ossOnly) {
diff --git a/packages/kbn-pm/src/config.ts b/packages/kbn-pm/src/config.ts
index a11b2ad9c72c3..666a2fed7a33c 100644
--- a/packages/kbn-pm/src/config.ts
+++ b/packages/kbn-pm/src/config.ts
@@ -31,6 +31,7 @@ export function getProjectPaths({ rootPath, ossOnly, skipKibanaPlugins }: Option
// correct and the expect behavior.
projectPaths.push(resolve(rootPath, 'test/plugin_functional/plugins/*'));
projectPaths.push(resolve(rootPath, 'test/interpreter_functional/plugins/*'));
+ projectPaths.push(resolve(rootPath, 'test/server_integration/__fixtures__/plugins/*'));
projectPaths.push(resolve(rootPath, 'examples/*'));
if (!ossOnly) {
diff --git a/packages/kbn-tinymath/grammar/grammar.peggy b/packages/kbn-tinymath/grammar/grammar.peggy
index 1c6f8c3334c23..414bc2fa11cb7 100644
--- a/packages/kbn-tinymath/grammar/grammar.peggy
+++ b/packages/kbn-tinymath/grammar/grammar.peggy
@@ -43,7 +43,7 @@ Literal "literal"
// Quoted variables are interpreted as strings
// but unquoted variables are more restrictive
Variable
- = _ [\'] chars:(ValidChar / Space / [\"])* [\'] _ {
+ = _ '"' chars:("\\\"" { return "\""; } / [^"])* '"' _ {
return {
type: 'variable',
value: chars.join(''),
@@ -51,7 +51,7 @@ Variable
text: text()
};
}
- / _ [\"] chars:(ValidChar / Space / [\'])* [\"] _ {
+ / _ "'" chars:("\\\'" { return "\'"; } / [^'])* "'" _ {
return {
type: 'variable',
value: chars.join(''),
diff --git a/packages/kbn-tinymath/test/library.test.js b/packages/kbn-tinymath/test/library.test.js
index bbc8503684fd4..9d87919c4f1ac 100644
--- a/packages/kbn-tinymath/test/library.test.js
+++ b/packages/kbn-tinymath/test/library.test.js
@@ -92,6 +92,7 @@ describe('Parser', () => {
expect(parse('@foo0')).toEqual(variableEqual('@foo0'));
expect(parse('.foo0')).toEqual(variableEqual('.foo0'));
expect(parse('-foo0')).toEqual(variableEqual('-foo0'));
+ expect(() => parse(`foo😀\t')`)).toThrow('Failed to parse');
});
});
@@ -103,6 +104,7 @@ describe('Parser', () => {
expect(parse('"foo bar fizz buzz"')).toEqual(variableEqual('foo bar fizz buzz'));
expect(parse('"foo bar baby"')).toEqual(variableEqual('foo bar baby'));
expect(parse(`"f'oo"`)).toEqual(variableEqual(`f'oo`));
+ expect(parse(`"foo😀\t"`)).toEqual(variableEqual(`foo😀\t`));
});
it('strings with single quotes', () => {
@@ -119,6 +121,7 @@ describe('Parser', () => {
expect(parse("'foo bar '")).toEqual(variableEqual("foo bar "));
expect(parse("'0foo'")).toEqual(variableEqual("0foo"));
expect(parse(`'f"oo'`)).toEqual(variableEqual(`f"oo`));
+ expect(parse(`'foo😀\t'`)).toEqual(variableEqual(`foo😀\t`));
/* eslint-enable prettier/prettier */
});
diff --git a/packages/kbn-ui-shared-deps/src/entry.js b/packages/kbn-ui-shared-deps/src/entry.js
index b8d21a473c65f..0e91c45ae6392 100644
--- a/packages/kbn-ui-shared-deps/src/entry.js
+++ b/packages/kbn-ui-shared-deps/src/entry.js
@@ -40,6 +40,7 @@ export const ElasticEui = require('@elastic/eui');
export const ElasticEuiLibServices = require('@elastic/eui/lib/services');
export const ElasticEuiLibServicesFormat = require('@elastic/eui/lib/services/format');
export const ElasticEuiChartsTheme = require('@elastic/eui/dist/eui_charts_theme');
+export const ReactBeautifulDnD = require('react-beautiful-dnd');
export const Theme = require('./theme.ts');
export const Lodash = require('lodash');
export const LodashFp = require('lodash/fp');
diff --git a/packages/kbn-ui-shared-deps/src/index.js b/packages/kbn-ui-shared-deps/src/index.js
index c5853dc091875..36c2e6b02879e 100644
--- a/packages/kbn-ui-shared-deps/src/index.js
+++ b/packages/kbn-ui-shared-deps/src/index.js
@@ -85,6 +85,8 @@ exports.externals = {
'@elastic/eui/dist/eui_charts_theme': '__kbnSharedDeps__.ElasticEuiChartsTheme',
'@elastic/eui/dist/eui_theme_light.json': '__kbnSharedDeps__.Theme.euiLightVars',
'@elastic/eui/dist/eui_theme_dark.json': '__kbnSharedDeps__.Theme.euiDarkVars',
+ // transient dep of eui
+ 'react-beautiful-dnd': '__kbnSharedDeps__.ReactBeautifulDnD',
lodash: '__kbnSharedDeps__.Lodash',
'lodash/fp': '__kbnSharedDeps__.LodashFp',
fflate: '__kbnSharedDeps__.Fflate',
diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
index 3668829a6888c..0b10209bc13e5 100644
--- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
+++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
@@ -370,54 +370,62 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
isOpen={true}
onClose={[Function]}
>
-
-
-
- }
- />
-
-
-
"`;
+exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"
Flyout content
"`;
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = `
Array [
@@ -59,4 +59,4 @@ Array [
]
`;
-exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
Flyout content 2
"`;
+exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
Flyout content 2
"`;
diff --git a/src/core/public/styles/_base.scss b/src/core/public/styles/_base.scss
index 3386fa73f328a..de138cdf402e6 100644
--- a/src/core/public/styles/_base.scss
+++ b/src/core/public/styles/_base.scss
@@ -26,7 +26,7 @@
}
.euiBody--collapsibleNavIsDocked .euiBottomBar {
- margin-left: $euiCollapsibleNavWidth;
+ margin-left: 320px; // Hard-coded for now -- @cchaos
}
// Temporary fix for EuiPageHeader with a bottom border but no tabs or padding
diff --git a/src/core/server/csp/config.test.ts b/src/core/server/csp/config.test.ts
index 8036ebeeaad31..6db93addb7da8 100644
--- a/src/core/server/csp/config.test.ts
+++ b/src/core/server/csp/config.test.ts
@@ -9,11 +9,469 @@
import { config } from './config';
describe('config.validate()', () => {
- test(`does not allow "disableEmbedding" to be set to true`, () => {
+ it(`does not allow "disableEmbedding" to be set to true`, () => {
// This is intentionally not editable in the raw CSP config.
// Users should set `server.securityResponseHeaders.disableEmbedding` to control this config property.
expect(() => config.schema.validate({ disableEmbedding: true })).toThrowError(
'[disableEmbedding]: expected value to equal [false]'
);
});
+
+ describe(`"script_src"`, () => {
+ it(`throws if containing 'unsafe-inline' when 'strict' is true`, () => {
+ expect(() =>
+ config.schema.validate({
+ strict: true,
+ warnLegacyBrowsers: false,
+ script_src: [`'self'`, `unsafe-inline`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.strict\` is true"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ strict: true,
+ warnLegacyBrowsers: false,
+ script_src: [`'self'`, `'unsafe-inline'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.strict\` is true"`
+ );
+ });
+
+ it(`throws if containing 'unsafe-inline' when 'warnLegacyBrowsers' is true`, () => {
+ expect(() =>
+ config.schema.validate({
+ strict: false,
+ warnLegacyBrowsers: true,
+ script_src: [`'self'`, `unsafe-inline`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.warnLegacyBrowsers\` is true"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ strict: false,
+ warnLegacyBrowsers: true,
+ script_src: [`'self'`, `'unsafe-inline'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.warnLegacyBrowsers\` is true"`
+ );
+ });
+
+ it(`does not throw if containing 'unsafe-inline' when 'strict' and 'warnLegacyBrowsers' are false`, () => {
+ expect(() =>
+ config.schema.validate({
+ strict: false,
+ warnLegacyBrowsers: false,
+ script_src: [`'self'`, `unsafe-inline`],
+ })
+ ).not.toThrow();
+
+ expect(() =>
+ config.schema.validate({
+ strict: false,
+ warnLegacyBrowsers: false,
+ script_src: [`'self'`, `'unsafe-inline'`],
+ })
+ ).not.toThrow();
+ });
+
+ it(`throws if 'rules' is also specified`, () => {
+ expect(() =>
+ config.schema.validate({
+ rules: [
+ `script-src 'unsafe-eval' 'self'`,
+ `worker-src 'unsafe-eval' 'self'`,
+ `style-src 'unsafe-eval' 'self'`,
+ ],
+ script_src: [`'self'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
+ );
+ });
+
+ it('throws if using an `nonce-*` value', () => {
+ expect(() =>
+ config.schema.validate({
+ script_src: [`hello`, `nonce-foo`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[script_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
+ );
+ });
+ it("throws if using `none` or `'none'`", () => {
+ expect(() =>
+ config.schema.validate({
+ script_src: [`hello`, `none`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[script_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ script_src: [`hello`, `'none'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[script_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+ });
+ });
+
+ describe(`"worker_src"`, () => {
+ it(`throws if 'rules' is also specified`, () => {
+ expect(() =>
+ config.schema.validate({
+ rules: [
+ `script-src 'unsafe-eval' 'self'`,
+ `worker-src 'unsafe-eval' 'self'`,
+ `style-src 'unsafe-eval' 'self'`,
+ ],
+ worker_src: [`'self'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
+ );
+ });
+
+ it('throws if using an `nonce-*` value', () => {
+ expect(() =>
+ config.schema.validate({
+ worker_src: [`hello`, `nonce-foo`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[worker_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
+ );
+ });
+ it("throws if using `none` or `'none'`", () => {
+ expect(() =>
+ config.schema.validate({
+ worker_src: [`hello`, `none`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[worker_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ worker_src: [`hello`, `'none'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[worker_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+ });
+ });
+
+ describe(`"style_src"`, () => {
+ it(`throws if 'rules' is also specified`, () => {
+ expect(() =>
+ config.schema.validate({
+ rules: [
+ `script-src 'unsafe-eval' 'self'`,
+ `worker-src 'unsafe-eval' 'self'`,
+ `style-src 'unsafe-eval' 'self'`,
+ ],
+ style_src: [`'self'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
+ );
+ });
+
+ it('throws if using an `nonce-*` value', () => {
+ expect(() =>
+ config.schema.validate({
+ style_src: [`hello`, `nonce-foo`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[style_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
+ );
+ });
+ it("throws if using `none` or `'none'`", () => {
+ expect(() =>
+ config.schema.validate({
+ style_src: [`hello`, `none`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[style_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ style_src: [`hello`, `'none'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[style_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+ });
+ });
+
+ describe(`"connect_src"`, () => {
+ it(`throws if 'rules' is also specified`, () => {
+ expect(() =>
+ config.schema.validate({
+ rules: [
+ `script-src 'unsafe-eval' 'self'`,
+ `worker-src 'unsafe-eval' 'self'`,
+ `style-src 'unsafe-eval' 'self'`,
+ ],
+ connect_src: [`'self'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
+ );
+ });
+
+ it('throws if using an `nonce-*` value', () => {
+ expect(() =>
+ config.schema.validate({
+ connect_src: [`hello`, `nonce-foo`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[connect_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
+ );
+ });
+ it("throws if using `none` or `'none'`", () => {
+ expect(() =>
+ config.schema.validate({
+ connect_src: [`hello`, `none`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[connect_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ connect_src: [`hello`, `'none'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[connect_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+ });
+ });
+
+ describe(`"default_src"`, () => {
+ it(`throws if 'rules' is also specified`, () => {
+ expect(() =>
+ config.schema.validate({
+ rules: [
+ `script-src 'unsafe-eval' 'self'`,
+ `worker-src 'unsafe-eval' 'self'`,
+ `style-src 'unsafe-eval' 'self'`,
+ ],
+ default_src: [`'self'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
+ );
+ });
+
+ it('throws if using an `nonce-*` value', () => {
+ expect(() =>
+ config.schema.validate({
+ default_src: [`hello`, `nonce-foo`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[default_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
+ );
+ });
+ it("throws if using `none` or `'none'`", () => {
+ expect(() =>
+ config.schema.validate({
+ default_src: [`hello`, `none`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[default_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ default_src: [`hello`, `'none'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[default_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+ });
+ });
+
+ describe(`"font_src"`, () => {
+ it(`throws if 'rules' is also specified`, () => {
+ expect(() =>
+ config.schema.validate({
+ rules: [
+ `script-src 'unsafe-eval' 'self'`,
+ `worker-src 'unsafe-eval' 'self'`,
+ `style-src 'unsafe-eval' 'self'`,
+ ],
+ font_src: [`'self'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
+ );
+ });
+
+ it('throws if using an `nonce-*` value', () => {
+ expect(() =>
+ config.schema.validate({
+ font_src: [`hello`, `nonce-foo`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[font_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
+ );
+ });
+ it("throws if using `none` or `'none'`", () => {
+ expect(() =>
+ config.schema.validate({
+ font_src: [`hello`, `none`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[font_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ font_src: [`hello`, `'none'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[font_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+ });
+ });
+
+ describe(`"frame_src"`, () => {
+ it(`throws if 'rules' is also specified`, () => {
+ expect(() =>
+ config.schema.validate({
+ rules: [
+ `script-src 'unsafe-eval' 'self'`,
+ `worker-src 'unsafe-eval' 'self'`,
+ `style-src 'unsafe-eval' 'self'`,
+ ],
+ frame_src: [`'self'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
+ );
+ });
+
+ it('throws if using an `nonce-*` value', () => {
+ expect(() =>
+ config.schema.validate({
+ frame_src: [`hello`, `nonce-foo`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[frame_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
+ );
+ });
+ it("throws if using `none` or `'none'`", () => {
+ expect(() =>
+ config.schema.validate({
+ frame_src: [`hello`, `none`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[frame_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ frame_src: [`hello`, `'none'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[frame_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+ });
+ });
+
+ describe(`"img_src"`, () => {
+ it(`throws if 'rules' is also specified`, () => {
+ expect(() =>
+ config.schema.validate({
+ rules: [
+ `script-src 'unsafe-eval' 'self'`,
+ `worker-src 'unsafe-eval' 'self'`,
+ `style-src 'unsafe-eval' 'self'`,
+ ],
+ img_src: [`'self'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
+ );
+ });
+
+ it('throws if using an `nonce-*` value', () => {
+ expect(() =>
+ config.schema.validate({
+ img_src: [`hello`, `nonce-foo`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[img_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
+ );
+ });
+ it("throws if using `none` or `'none'`", () => {
+ expect(() =>
+ config.schema.validate({
+ img_src: [`hello`, `none`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[img_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ img_src: [`hello`, `'none'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[img_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+ });
+ });
+
+ describe(`"frame_ancestors"`, () => {
+ it(`throws if 'rules' is also specified`, () => {
+ expect(() =>
+ config.schema.validate({
+ rules: [
+ `script-src 'unsafe-eval' 'self'`,
+ `worker-src 'unsafe-eval' 'self'`,
+ `style-src 'unsafe-eval' 'self'`,
+ ],
+ frame_ancestors: [`'self'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
+ );
+ });
+
+ it('throws if using an `nonce-*` value', () => {
+ expect(() =>
+ config.schema.validate({
+ frame_ancestors: [`hello`, `nonce-foo`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[frame_ancestors]: using \\"nonce-*\\" is considered insecure and is not allowed"`
+ );
+ });
+ it("throws if using `none` or `'none'`", () => {
+ expect(() =>
+ config.schema.validate({
+ frame_ancestors: [`hello`, `none`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[frame_ancestors]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+
+ expect(() =>
+ config.schema.validate({
+ frame_ancestors: [`hello`, `'none'`],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[frame_ancestors]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
+ );
+ });
+ });
});
diff --git a/src/core/server/csp/config.ts b/src/core/server/csp/config.ts
index a61fa1b03a45c..3a7cb20985cea 100644
--- a/src/core/server/csp/config.ts
+++ b/src/core/server/csp/config.ts
@@ -7,28 +7,150 @@
*/
import { TypeOf, schema } from '@kbn/config-schema';
+import { ServiceConfigDescriptor } from '../internal_types';
+
+interface DirectiveValidationOptions {
+ allowNone: boolean;
+ allowNonce: boolean;
+}
+
+const getDirectiveValidator = (options: DirectiveValidationOptions) => {
+ const validateValue = getDirectiveValueValidator(options);
+ return (values: string[]) => {
+ for (const value of values) {
+ const error = validateValue(value);
+ if (error) {
+ return error;
+ }
+ }
+ };
+};
+
+const getDirectiveValueValidator = ({ allowNone, allowNonce }: DirectiveValidationOptions) => {
+ return (value: string) => {
+ if (!allowNonce && value.startsWith('nonce-')) {
+ return `using "nonce-*" is considered insecure and is not allowed`;
+ }
+ if (!allowNone && (value === `none` || value === `'none'`)) {
+ return `using "none" would conflict with Kibana's default csp configuration and is not allowed`;
+ }
+ };
+};
+
+const configSchema = schema.object(
+ {
+ rules: schema.maybe(schema.arrayOf(schema.string())),
+ script_src: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
+ }),
+ worker_src: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
+ }),
+ style_src: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
+ }),
+ connect_src: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
+ }),
+ default_src: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
+ }),
+ font_src: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
+ }),
+ frame_src: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
+ }),
+ img_src: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
+ }),
+ frame_ancestors: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
+ }),
+ report_uri: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ validate: getDirectiveValidator({ allowNone: true, allowNonce: false }),
+ }),
+ report_to: schema.arrayOf(schema.string(), {
+ defaultValue: [],
+ }),
+ strict: schema.boolean({ defaultValue: true }),
+ warnLegacyBrowsers: schema.boolean({ defaultValue: true }),
+ disableEmbedding: schema.oneOf([schema.literal(false)], { defaultValue: false }),
+ },
+ {
+ validate: (cspConfig) => {
+ if (cspConfig.rules && hasDirectiveSpecified(cspConfig)) {
+ return `"csp.rules" cannot be used when specifying per-directive additions such as "script_src", "worker_src" or "style_src"`;
+ }
+ const hasUnsafeInlineScriptSrc =
+ cspConfig.script_src.includes(`unsafe-inline`) ||
+ cspConfig.script_src.includes(`'unsafe-inline'`);
+
+ if (cspConfig.strict && hasUnsafeInlineScriptSrc) {
+ return 'cannot use `unsafe-inline` for `script_src` when `csp.strict` is true';
+ }
+ if (cspConfig.warnLegacyBrowsers && hasUnsafeInlineScriptSrc) {
+ return 'cannot use `unsafe-inline` for `script_src` when `csp.warnLegacyBrowsers` is true';
+ }
+ },
+ }
+);
+
+const hasDirectiveSpecified = (rawConfig: CspConfigType): boolean => {
+ return Boolean(
+ rawConfig.script_src.length ||
+ rawConfig.worker_src.length ||
+ rawConfig.style_src.length ||
+ rawConfig.connect_src.length ||
+ rawConfig.default_src.length ||
+ rawConfig.font_src.length ||
+ rawConfig.frame_src.length ||
+ rawConfig.img_src.length ||
+ rawConfig.frame_ancestors.length ||
+ rawConfig.report_uri.length ||
+ rawConfig.report_to.length
+ );
+};
/**
* @internal
*/
-export type CspConfigType = TypeOf;
+export type CspConfigType = TypeOf;
-export const config = {
+export const config: ServiceConfigDescriptor = {
// TODO: Move this to server.csp using config deprecations
// ? https://github.com/elastic/kibana/pull/52251
path: 'csp',
- schema: schema.object({
- rules: schema.arrayOf(schema.string(), {
- defaultValue: [
- `script-src 'unsafe-eval' 'self'`,
- `worker-src blob: 'self'`,
- `style-src 'unsafe-inline' 'self'`,
- ],
- }),
- strict: schema.boolean({ defaultValue: true }),
- warnLegacyBrowsers: schema.boolean({ defaultValue: true }),
- disableEmbedding: schema.oneOf([schema.literal(false)], { defaultValue: false }),
- }),
+ schema: configSchema,
+ deprecations: () => [
+ (rawConfig, fromPath, addDeprecation) => {
+ const cspConfig = rawConfig[fromPath];
+ if (cspConfig?.rules) {
+ addDeprecation({
+ message:
+ '`csp.rules` is deprecated in favor of directive specific configuration. Please use `csp.connect_src`, ' +
+ '`csp.default_src`, `csp.font_src`, `csp.frame_ancestors`, `csp.frame_src`, `csp.img_src`, ' +
+ '`csp.report_uri`, `csp.report_to`, `csp.script_src`, `csp.style_src`, and `csp.worker_src` instead.',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "csp.rules" from the Kibana config file."`,
+ `Add directive specific configurations to the config file using "csp.connect_src", "csp.default_src", "csp.font_src", ` +
+ `"csp.frame_ancestors", "csp.frame_src", "csp.img_src", "csp.report_uri", "csp.report_to", "csp.script_src", ` +
+ `"csp.style_src", and "csp.worker_src".`,
+ ],
+ },
+ });
+ }
+ },
+ ],
};
-
-export const FRAME_ANCESTORS_RULE = `frame-ancestors 'self'`; // only used by CspConfig when embedding is disabled
diff --git a/src/core/server/csp/csp_config.test.ts b/src/core/server/csp/csp_config.test.ts
index 1e023c6f08ea8..a1bac7d4ae73e 100644
--- a/src/core/server/csp/csp_config.test.ts
+++ b/src/core/server/csp/csp_config.test.ts
@@ -7,7 +7,7 @@
*/
import { CspConfig } from './csp_config';
-import { FRAME_ANCESTORS_RULE } from './config';
+import { config as cspConfig, CspConfigType } from './config';
// CSP rules aren't strictly additive, so any change can potentially expand or
// restrict the policy in a way we consider a breaking change. For that reason,
@@ -23,6 +23,12 @@ import { FRAME_ANCESTORS_RULE } from './config';
// the nature of a change in defaults during a PR review.
describe('CspConfig', () => {
+ let defaultConfig: CspConfigType;
+
+ beforeEach(() => {
+ defaultConfig = cspConfig.schema.validate({});
+ });
+
test('DEFAULT', () => {
expect(CspConfig.DEFAULT).toMatchInlineSnapshot(`
CspConfig {
@@ -40,50 +46,129 @@ describe('CspConfig', () => {
});
test('defaults from config', () => {
- expect(new CspConfig()).toEqual(CspConfig.DEFAULT);
+ expect(new CspConfig(defaultConfig)).toEqual(CspConfig.DEFAULT);
});
describe('partial config', () => {
test('allows "rules" to be set and changes header', () => {
- const rules = ['foo', 'bar'];
- const config = new CspConfig({ rules });
+ const rules = [`foo 'self'`, `bar 'self'`];
+ const config = new CspConfig({ ...defaultConfig, rules });
expect(config.rules).toEqual(rules);
- expect(config.header).toMatchInlineSnapshot(`"foo; bar"`);
+ expect(config.header).toMatchInlineSnapshot(`"foo 'self'; bar 'self'"`);
});
test('allows "strict" to be set', () => {
- const config = new CspConfig({ strict: false });
+ const config = new CspConfig({ ...defaultConfig, strict: false });
expect(config.strict).toEqual(false);
expect(config.strict).not.toEqual(CspConfig.DEFAULT.strict);
});
test('allows "warnLegacyBrowsers" to be set', () => {
const warnLegacyBrowsers = false;
- const config = new CspConfig({ warnLegacyBrowsers });
+ const config = new CspConfig({ ...defaultConfig, warnLegacyBrowsers });
expect(config.warnLegacyBrowsers).toEqual(warnLegacyBrowsers);
expect(config.warnLegacyBrowsers).not.toEqual(CspConfig.DEFAULT.warnLegacyBrowsers);
});
+ test('allows "worker_src" to be set and changes header', () => {
+ const config = new CspConfig({
+ ...defaultConfig,
+ rules: [],
+ worker_src: ['foo', 'bar'],
+ });
+ expect(config.rules).toEqual([`worker-src foo bar`]);
+ expect(config.header).toEqual(`worker-src foo bar`);
+ });
+
+ test('allows "style_src" to be set and changes header', () => {
+ const config = new CspConfig({
+ ...defaultConfig,
+ rules: [],
+ style_src: ['foo', 'bar'],
+ });
+ expect(config.rules).toEqual([`style-src foo bar`]);
+ expect(config.header).toEqual(`style-src foo bar`);
+ });
+
+ test('allows "script_src" to be set and changes header', () => {
+ const config = new CspConfig({
+ ...defaultConfig,
+ rules: [],
+ script_src: ['foo', 'bar'],
+ });
+ expect(config.rules).toEqual([`script-src foo bar`]);
+ expect(config.header).toEqual(`script-src foo bar`);
+ });
+
+ test('allows all directives to be set and changes header', () => {
+ const config = new CspConfig({
+ ...defaultConfig,
+ rules: [],
+ script_src: ['script', 'foo'],
+ worker_src: ['worker', 'bar'],
+ style_src: ['style', 'dolly'],
+ });
+ expect(config.rules).toEqual([
+ `script-src script foo`,
+ `worker-src worker bar`,
+ `style-src style dolly`,
+ ]);
+ expect(config.header).toEqual(
+ `script-src script foo; worker-src worker bar; style-src style dolly`
+ );
+ });
+
+ test('applies defaults when `rules` is undefined', () => {
+ const config = new CspConfig({
+ ...defaultConfig,
+ rules: undefined,
+ script_src: ['script'],
+ worker_src: ['worker'],
+ style_src: ['style'],
+ });
+ expect(config.rules).toEqual([
+ `script-src 'unsafe-eval' 'self' script`,
+ `worker-src blob: 'self' worker`,
+ `style-src 'unsafe-inline' 'self' style`,
+ ]);
+ expect(config.header).toEqual(
+ `script-src 'unsafe-eval' 'self' script; worker-src blob: 'self' worker; style-src 'unsafe-inline' 'self' style`
+ );
+ });
+
describe('allows "disableEmbedding" to be set', () => {
const disableEmbedding = true;
test('and changes rules/header if custom rules are not defined', () => {
- const config = new CspConfig({ disableEmbedding });
+ const config = new CspConfig({ ...defaultConfig, disableEmbedding });
expect(config.disableEmbedding).toEqual(disableEmbedding);
expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding);
- expect(config.rules).toEqual(expect.arrayContaining([FRAME_ANCESTORS_RULE]));
+ expect(config.rules).toEqual(expect.arrayContaining([`frame-ancestors 'self'`]));
expect(config.header).toMatchInlineSnapshot(
`"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'; frame-ancestors 'self'"`
);
});
test('and does not change rules/header if custom rules are defined', () => {
- const rules = ['foo', 'bar'];
- const config = new CspConfig({ disableEmbedding, rules });
+ const rules = [`foo 'self'`, `bar 'self'`];
+ const config = new CspConfig({ ...defaultConfig, disableEmbedding, rules });
expect(config.disableEmbedding).toEqual(disableEmbedding);
expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding);
expect(config.rules).toEqual(rules);
- expect(config.header).toMatchInlineSnapshot(`"foo; bar"`);
+ expect(config.header).toMatchInlineSnapshot(`"foo 'self'; bar 'self'"`);
+ });
+
+ test('and overrides `frame-ancestors` if set', () => {
+ const config = new CspConfig({
+ ...defaultConfig,
+ disableEmbedding: true,
+ frame_ancestors: ['foo.com'],
+ });
+ expect(config.disableEmbedding).toEqual(disableEmbedding);
+ expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding);
+ expect(config.header).toMatchInlineSnapshot(
+ `"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'; frame-ancestors 'self'"`
+ );
});
});
});
diff --git a/src/core/server/csp/csp_config.ts b/src/core/server/csp/csp_config.ts
index 649c81576ef52..13778088d9df2 100644
--- a/src/core/server/csp/csp_config.ts
+++ b/src/core/server/csp/csp_config.ts
@@ -6,7 +6,8 @@
* Side Public License, v 1.
*/
-import { config, FRAME_ANCESTORS_RULE } from './config';
+import { config, CspConfigType } from './config';
+import { CspDirectives } from './csp_directives';
const DEFAULT_CONFIG = Object.freeze(config.schema.validate({}));
@@ -50,8 +51,9 @@ export interface ICspConfig {
* @public
*/
export class CspConfig implements ICspConfig {
- static readonly DEFAULT = new CspConfig();
+ static readonly DEFAULT = new CspConfig(DEFAULT_CONFIG);
+ readonly #directives: CspDirectives;
public readonly rules: string[];
public readonly strict: boolean;
public readonly warnLegacyBrowsers: boolean;
@@ -62,16 +64,18 @@ export class CspConfig implements ICspConfig {
* Returns the default CSP configuration when passed with no config
* @internal
*/
- constructor(rawCspConfig: Partial> = {}) {
- const source = { ...DEFAULT_CONFIG, ...rawCspConfig };
-
- this.rules = [...source.rules];
- this.strict = source.strict;
- this.warnLegacyBrowsers = source.warnLegacyBrowsers;
- this.disableEmbedding = source.disableEmbedding;
- if (!rawCspConfig.rules?.length && source.disableEmbedding) {
- this.rules.push(FRAME_ANCESTORS_RULE);
+ constructor(rawCspConfig: CspConfigType) {
+ this.#directives = CspDirectives.fromConfig(rawCspConfig);
+ if (!rawCspConfig.rules?.length && rawCspConfig.disableEmbedding) {
+ this.#directives.clearDirectiveValues('frame-ancestors');
+ this.#directives.addDirectiveValue('frame-ancestors', `'self'`);
}
- this.header = this.rules.join('; ');
+
+ this.rules = this.#directives.getRules();
+ this.header = this.#directives.getCspHeader();
+
+ this.strict = rawCspConfig.strict;
+ this.warnLegacyBrowsers = rawCspConfig.warnLegacyBrowsers;
+ this.disableEmbedding = rawCspConfig.disableEmbedding;
}
}
diff --git a/src/core/server/csp/csp_directives.test.ts b/src/core/server/csp/csp_directives.test.ts
new file mode 100644
index 0000000000000..1077b6ea9f3cd
--- /dev/null
+++ b/src/core/server/csp/csp_directives.test.ts
@@ -0,0 +1,266 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { CspDirectives } from './csp_directives';
+import { config as cspConfig } from './config';
+
+describe('CspDirectives', () => {
+ describe('#addDirectiveValue', () => {
+ it('properly updates the rules', () => {
+ const directives = new CspDirectives();
+ directives.addDirectiveValue('style-src', 'foo');
+
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "style-src foo",
+ ]
+ `);
+
+ directives.addDirectiveValue('style-src', 'bar');
+
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "style-src foo bar",
+ ]
+ `);
+ });
+
+ it('properly updates the header', () => {
+ const directives = new CspDirectives();
+ directives.addDirectiveValue('style-src', 'foo');
+
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo"`);
+
+ directives.addDirectiveValue('style-src', 'bar');
+
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo bar"`);
+ });
+
+ it('handles distinct directives', () => {
+ const directives = new CspDirectives();
+ directives.addDirectiveValue('style-src', 'foo');
+ directives.addDirectiveValue('style-src', 'bar');
+ directives.addDirectiveValue('worker-src', 'dolly');
+
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(
+ `"style-src foo bar; worker-src dolly"`
+ );
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "style-src foo bar",
+ "worker-src dolly",
+ ]
+ `);
+ });
+
+ it('removes duplicates', () => {
+ const directives = new CspDirectives();
+ directives.addDirectiveValue('style-src', 'foo');
+ directives.addDirectiveValue('style-src', 'foo');
+ directives.addDirectiveValue('style-src', 'bar');
+
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo bar"`);
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "style-src foo bar",
+ ]
+ `);
+ });
+
+ it('automatically adds single quotes for keywords', () => {
+ const directives = new CspDirectives();
+ directives.addDirectiveValue('style-src', 'none');
+ directives.addDirectiveValue('style-src', 'self');
+ directives.addDirectiveValue('style-src', 'strict-dynamic');
+ directives.addDirectiveValue('style-src', 'report-sample');
+ directives.addDirectiveValue('style-src', 'unsafe-inline');
+ directives.addDirectiveValue('style-src', 'unsafe-eval');
+ directives.addDirectiveValue('style-src', 'unsafe-hashes');
+ directives.addDirectiveValue('style-src', 'unsafe-allow-redirects');
+
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(
+ `"style-src 'none' 'self' 'strict-dynamic' 'report-sample' 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes' 'unsafe-allow-redirects'"`
+ );
+ });
+
+ it('does not add single quotes for keywords when already present', () => {
+ const directives = new CspDirectives();
+ directives.addDirectiveValue('style-src', `'none'`);
+ directives.addDirectiveValue('style-src', `'self'`);
+ directives.addDirectiveValue('style-src', `'strict-dynamic'`);
+ directives.addDirectiveValue('style-src', `'report-sample'`);
+ directives.addDirectiveValue('style-src', `'unsafe-inline'`);
+ directives.addDirectiveValue('style-src', `'unsafe-eval'`);
+ directives.addDirectiveValue('style-src', `'unsafe-hashes'`);
+ directives.addDirectiveValue('style-src', `'unsafe-allow-redirects'`);
+
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(
+ `"style-src 'none' 'self' 'strict-dynamic' 'report-sample' 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes' 'unsafe-allow-redirects'"`
+ );
+ });
+ });
+
+ describe('#fromConfig', () => {
+ it('returns the correct rules for the default config', () => {
+ const config = cspConfig.schema.validate({});
+ const directives = CspDirectives.fromConfig(config);
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "script-src 'unsafe-eval' 'self'",
+ "worker-src blob: 'self'",
+ "style-src 'unsafe-inline' 'self'",
+ ]
+ `);
+ });
+
+ it('returns the correct header for the default config', () => {
+ const config = cspConfig.schema.validate({});
+ const directives = CspDirectives.fromConfig(config);
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(
+ `"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'"`
+ );
+ });
+
+ it('handles config with rules', () => {
+ const config = cspConfig.schema.validate({
+ rules: [`script-src 'self' http://foo.com`, `worker-src 'self'`],
+ });
+ const directives = CspDirectives.fromConfig(config);
+
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "script-src 'self' http://foo.com",
+ "worker-src 'self'",
+ ]
+ `);
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(
+ `"script-src 'self' http://foo.com; worker-src 'self'"`
+ );
+ });
+
+ it('adds single quotes for keyword for rules', () => {
+ const config = cspConfig.schema.validate({
+ rules: [`script-src self http://foo.com`, `worker-src self`],
+ });
+ const directives = CspDirectives.fromConfig(config);
+
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "script-src 'self' http://foo.com",
+ "worker-src 'self'",
+ ]
+ `);
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(
+ `"script-src 'self' http://foo.com; worker-src 'self'"`
+ );
+ });
+
+ it('handles multiple whitespaces when parsing rules', () => {
+ const config = cspConfig.schema.validate({
+ rules: [` script-src 'self' http://foo.com `, ` worker-src 'self' `],
+ });
+ const directives = CspDirectives.fromConfig(config);
+
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "script-src 'self' http://foo.com",
+ "worker-src 'self'",
+ ]
+ `);
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(
+ `"script-src 'self' http://foo.com; worker-src 'self'"`
+ );
+ });
+
+ it('supports unregistered directives', () => {
+ const config = cspConfig.schema.validate({
+ rules: [`script-src 'self' http://foo.com`, `img-src 'self'`, 'foo bar'],
+ });
+ const directives = CspDirectives.fromConfig(config);
+
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "script-src 'self' http://foo.com",
+ "img-src 'self'",
+ "foo bar",
+ ]
+ `);
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(
+ `"script-src 'self' http://foo.com; img-src 'self'; foo bar"`
+ );
+ });
+
+ it('adds default value for config with directives', () => {
+ const config = cspConfig.schema.validate({
+ script_src: [`baz`],
+ worker_src: [`foo`],
+ style_src: [`bar`, `dolly`],
+ });
+ const directives = CspDirectives.fromConfig(config);
+
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "script-src 'unsafe-eval' 'self' baz",
+ "worker-src blob: 'self' foo",
+ "style-src 'unsafe-inline' 'self' bar dolly",
+ ]
+ `);
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(
+ `"script-src 'unsafe-eval' 'self' baz; worker-src blob: 'self' foo; style-src 'unsafe-inline' 'self' bar dolly"`
+ );
+ });
+
+ it('adds additional values for some directives without defaults', () => {
+ const config = cspConfig.schema.validate({
+ connect_src: [`connect-src`],
+ default_src: [`default-src`],
+ font_src: [`font-src`],
+ frame_src: [`frame-src`],
+ img_src: [`img-src`],
+ frame_ancestors: [`frame-ancestors`],
+ report_uri: [`report-uri`],
+ report_to: [`report-to`],
+ });
+ const directives = CspDirectives.fromConfig(config);
+
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "script-src 'unsafe-eval' 'self'",
+ "worker-src blob: 'self'",
+ "style-src 'unsafe-inline' 'self'",
+ "connect-src 'self' connect-src",
+ "default-src 'self' default-src",
+ "font-src 'self' font-src",
+ "frame-src 'self' frame-src",
+ "img-src 'self' img-src",
+ "frame-ancestors 'self' frame-ancestors",
+ "report-uri report-uri",
+ "report-to report-to",
+ ]
+ `);
+ });
+
+ it('adds single quotes for keywords in added directives', () => {
+ const config = cspConfig.schema.validate({
+ script_src: [`unsafe-hashes`],
+ });
+ const directives = CspDirectives.fromConfig(config);
+
+ expect(directives.getRules()).toMatchInlineSnapshot(`
+ Array [
+ "script-src 'unsafe-eval' 'self' 'unsafe-hashes'",
+ "worker-src blob: 'self'",
+ "style-src 'unsafe-inline' 'self'",
+ ]
+ `);
+ expect(directives.getCspHeader()).toMatchInlineSnapshot(
+ `"script-src 'unsafe-eval' 'self' 'unsafe-hashes'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'"`
+ );
+ });
+ });
+});
diff --git a/src/core/server/csp/csp_directives.ts b/src/core/server/csp/csp_directives.ts
new file mode 100644
index 0000000000000..9e3b60f7f1e4f
--- /dev/null
+++ b/src/core/server/csp/csp_directives.ts
@@ -0,0 +1,159 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { CspConfigType } from './config';
+
+export type CspDirectiveName =
+ | 'script-src'
+ | 'worker-src'
+ | 'style-src'
+ | 'frame-ancestors'
+ | 'connect-src'
+ | 'default-src'
+ | 'font-src'
+ | 'frame-src'
+ | 'img-src'
+ | 'report-uri'
+ | 'report-to';
+
+/**
+ * The default rules that are always applied
+ */
+export const defaultRules: Partial> = {
+ 'script-src': [`'unsafe-eval'`, `'self'`],
+ 'worker-src': [`blob:`, `'self'`],
+ 'style-src': [`'unsafe-inline'`, `'self'`],
+};
+
+/**
+ * Per-directive rules that will be added when the configuration contains at least one value
+ * Main purpose is to add `self` value to some directives when the configuration specifies other values
+ */
+export const additionalRules: Partial> = {
+ 'connect-src': [`'self'`],
+ 'default-src': [`'self'`],
+ 'font-src': [`'self'`],
+ 'img-src': [`'self'`],
+ 'frame-ancestors': [`'self'`],
+ 'frame-src': [`'self'`],
+};
+
+export class CspDirectives {
+ private readonly directives = new Map>();
+
+ addDirectiveValue(directiveName: CspDirectiveName, directiveValue: string) {
+ if (!this.directives.has(directiveName)) {
+ this.directives.set(directiveName, new Set());
+ }
+ this.directives.get(directiveName)!.add(normalizeDirectiveValue(directiveValue));
+ }
+
+ clearDirectiveValues(directiveName: CspDirectiveName) {
+ this.directives.delete(directiveName);
+ }
+
+ getCspHeader() {
+ return this.getRules().join('; ');
+ }
+
+ getRules() {
+ return [...this.directives.entries()].map(([name, values]) => {
+ return [name, ...values].join(' ');
+ });
+ }
+
+ static fromConfig(config: CspConfigType): CspDirectives {
+ const cspDirectives = new CspDirectives();
+
+ // adding `csp.rules` or `default` rules
+ const initialRules = config.rules ? parseRules(config.rules) : { ...defaultRules };
+ Object.entries(initialRules).forEach(([key, values]) => {
+ values?.forEach((value) => {
+ cspDirectives.addDirectiveValue(key as CspDirectiveName, value);
+ });
+ });
+
+ // adding per-directive configuration
+ const additiveConfig = parseConfigDirectives(config);
+ [...additiveConfig.entries()].forEach(([directiveName, directiveValues]) => {
+ const additionalValues = additionalRules[directiveName] ?? [];
+ [...additionalValues, ...directiveValues].forEach((value) => {
+ cspDirectives.addDirectiveValue(directiveName, value);
+ });
+ });
+
+ return cspDirectives;
+ }
+}
+
+const parseRules = (rules: string[]): Partial> => {
+ const directives: Partial> = {};
+ rules.forEach((rule) => {
+ const [name, ...values] = rule.replace(/\s+/g, ' ').trim().split(' ');
+ directives[name as CspDirectiveName] = values;
+ });
+ return directives;
+};
+
+const parseConfigDirectives = (cspConfig: CspConfigType): Map => {
+ const map = new Map();
+
+ if (cspConfig.script_src?.length) {
+ map.set('script-src', cspConfig.script_src);
+ }
+ if (cspConfig.worker_src?.length) {
+ map.set('worker-src', cspConfig.worker_src);
+ }
+ if (cspConfig.style_src?.length) {
+ map.set('style-src', cspConfig.style_src);
+ }
+ if (cspConfig.connect_src?.length) {
+ map.set('connect-src', cspConfig.connect_src);
+ }
+ if (cspConfig.default_src?.length) {
+ map.set('default-src', cspConfig.default_src);
+ }
+ if (cspConfig.font_src?.length) {
+ map.set('font-src', cspConfig.font_src);
+ }
+ if (cspConfig.frame_src?.length) {
+ map.set('frame-src', cspConfig.frame_src);
+ }
+ if (cspConfig.img_src?.length) {
+ map.set('img-src', cspConfig.img_src);
+ }
+ if (cspConfig.frame_ancestors?.length) {
+ map.set('frame-ancestors', cspConfig.frame_ancestors);
+ }
+ if (cspConfig.report_uri?.length) {
+ map.set('report-uri', cspConfig.report_uri);
+ }
+ if (cspConfig.report_to?.length) {
+ map.set('report-to', cspConfig.report_to);
+ }
+
+ return map;
+};
+
+const keywordTokens = [
+ 'none',
+ 'self',
+ 'strict-dynamic',
+ 'report-sample',
+ 'unsafe-inline',
+ 'unsafe-eval',
+ 'unsafe-hashes',
+ 'unsafe-allow-redirects',
+];
+
+function normalizeDirectiveValue(value: string) {
+ if (keywordTokens.includes(value)) {
+ return `'${value}'`;
+ }
+ return value;
+}
diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts
index c802163866423..55af02a08561b 100644
--- a/src/core/server/http/cookie_session_storage.test.ts
+++ b/src/core/server/http/cookie_session_storage.test.ts
@@ -69,7 +69,11 @@ configService.atPath.mockImplementation((path) => {
} as any);
}
if (path === 'csp') {
- return new BehaviorSubject({} as any);
+ return new BehaviorSubject({
+ strict: false,
+ disableEmbedding: false,
+ warnLegacyBrowsers: true,
+ });
}
throw new Error(`Unexpected config path: ${path}`);
});
diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts
index 56095336d970b..06a4745632233 100644
--- a/src/core/server/http/http_config.test.ts
+++ b/src/core/server/http/http_config.test.ts
@@ -8,7 +8,7 @@
import uuid from 'uuid';
import { config, HttpConfig } from './http_config';
-import { CspConfig } from '../csp';
+import { config as cspConfig } from '../csp';
import { ExternalUrlConfig } from '../external_url';
const validHostnames = ['www.example.com', '8.8.8.8', '::1', 'localhost', '0.0.0.0'];
@@ -459,7 +459,8 @@ describe('HttpConfig', () => {
},
},
});
- const httpConfig = new HttpConfig(rawConfig, CspConfig.DEFAULT, ExternalUrlConfig.DEFAULT);
+ const rawCspConfig = cspConfig.schema.validate({});
+ const httpConfig = new HttpConfig(rawConfig, rawCspConfig, ExternalUrlConfig.DEFAULT);
expect(httpConfig.customResponseHeaders).toEqual({
string: 'string',
diff --git a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts
index cbd300fdc9c09..c2023c5577d61 100644
--- a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts
+++ b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts
@@ -79,7 +79,11 @@ describe('core lifecycle handlers', () => {
} as any);
}
if (path === 'csp') {
- return new BehaviorSubject({} as any);
+ return new BehaviorSubject({
+ strict: false,
+ disableEmbedding: false,
+ warnLegacyBrowsers: true,
+ });
}
throw new Error(`Unexpected config path: ${path}`);
});
diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts
index b3180b43d0026..4e1a88e967f8f 100644
--- a/src/core/server/http/test_utils.ts
+++ b/src/core/server/http/test_utils.ts
@@ -56,7 +56,11 @@ configService.atPath.mockImplementation((path) => {
} as any);
}
if (path === 'csp') {
- return new BehaviorSubject({} as any);
+ return new BehaviorSubject({
+ strict: false,
+ disableEmbedding: false,
+ warnLegacyBrowsers: true,
+ });
}
throw new Error(`Unexpected config path: ${path}`);
});
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index fcecf39f7e53a..3bc0b54635eb5 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -777,8 +777,12 @@ export interface CountResponse {
// @public
export class CspConfig implements ICspConfig {
+ // (undocumented)
+ #private;
+ // Warning: (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts
+ //
// @internal
- constructor(rawCspConfig?: Partial>);
+ constructor(rawCspConfig: CspConfigType);
// (undocumented)
static readonly DEFAULT: CspConfig;
// (undocumented)
diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts
index 534d7df9d9466..e1986c5bf1d92 100644
--- a/src/core/server/server.test.ts
+++ b/src/core/server/server.test.ts
@@ -114,6 +114,7 @@ test('runs services on "start"', async () => {
expect(mockSavedObjectsService.start).not.toHaveBeenCalled();
expect(mockUiSettingsService.start).not.toHaveBeenCalled();
expect(mockMetricsService.start).not.toHaveBeenCalled();
+ expect(mockStatusService.start).not.toHaveBeenCalled();
await server.start();
@@ -121,6 +122,7 @@ test('runs services on "start"', async () => {
expect(mockSavedObjectsService.start).toHaveBeenCalledTimes(1);
expect(mockUiSettingsService.start).toHaveBeenCalledTimes(1);
expect(mockMetricsService.start).toHaveBeenCalledTimes(1);
+ expect(mockStatusService.start).toHaveBeenCalledTimes(1);
});
test('does not fail on "setup" if there are unused paths detected', async () => {
diff --git a/src/core/server/server.ts b/src/core/server/server.ts
index adf794c390338..3f553dd90678e 100644
--- a/src/core/server/server.ts
+++ b/src/core/server/server.ts
@@ -248,6 +248,7 @@ export class Server {
savedObjects: savedObjectsStart,
exposedConfigsToUsage: this.plugins.getExposedPluginConfigsToUsage(),
});
+ this.status.start();
this.coreStart = {
capabilities: capabilitiesStart,
@@ -261,7 +262,6 @@ export class Server {
await this.plugins.start(this.coreStart);
- this.status.start();
await this.http.start();
startTransaction?.end();
diff --git a/src/core/server/status/plugins_status.test.ts b/src/core/server/status/plugins_status.test.ts
index b0d9e47876940..9dc1ddcddca3e 100644
--- a/src/core/server/status/plugins_status.test.ts
+++ b/src/core/server/status/plugins_status.test.ts
@@ -8,7 +8,7 @@
import { PluginName } from '../plugins';
import { PluginsStatusService } from './plugins_status';
-import { of, Observable, BehaviorSubject } from 'rxjs';
+import { of, Observable, BehaviorSubject, ReplaySubject } from 'rxjs';
import { ServiceStatusLevels, CoreStatus, ServiceStatus } from './types';
import { first } from 'rxjs/operators';
import { ServiceStatusLevelSnapshotSerializer } from './test_utils';
@@ -34,6 +34,28 @@ describe('PluginStatusService', () => {
['c', ['a', 'b']],
]);
+ describe('set', () => {
+ it('throws an exception if called after registrations are blocked', () => {
+ const service = new PluginsStatusService({
+ core$: coreAllAvailable$,
+ pluginDependencies,
+ });
+
+ service.blockNewRegistrations();
+ expect(() => {
+ service.set(
+ 'a',
+ of({
+ level: ServiceStatusLevels.available,
+ summary: 'fail!',
+ })
+ );
+ }).toThrowErrorMatchingInlineSnapshot(
+ `"Custom statuses cannot be registered after setup, plugin [a] attempted"`
+ );
+ });
+ });
+
describe('getDerivedStatus$', () => {
it(`defaults to core's most severe status`, async () => {
const serviceAvailable = new PluginsStatusService({
@@ -231,6 +253,75 @@ describe('PluginStatusService', () => {
{ a: { level: ServiceStatusLevels.available, summary: 'a available' } },
]);
});
+
+ it('updates when a plugin status observable emits', async () => {
+ const service = new PluginsStatusService({
+ core$: coreAllAvailable$,
+ pluginDependencies: new Map([['a', []]]),
+ });
+ const statusUpdates: Array> = [];
+ const subscription = service
+ .getAll$()
+ .subscribe((pluginStatuses) => statusUpdates.push(pluginStatuses));
+
+ const aStatus$ = new BehaviorSubject({
+ level: ServiceStatusLevels.degraded,
+ summary: 'a degraded',
+ });
+ service.set('a', aStatus$);
+ aStatus$.next({ level: ServiceStatusLevels.unavailable, summary: 'a unavailable' });
+ aStatus$.next({ level: ServiceStatusLevels.available, summary: 'a available' });
+ subscription.unsubscribe();
+
+ expect(statusUpdates).toEqual([
+ { a: { level: ServiceStatusLevels.available, summary: 'All dependencies are available' } },
+ { a: { level: ServiceStatusLevels.degraded, summary: 'a degraded' } },
+ { a: { level: ServiceStatusLevels.unavailable, summary: 'a unavailable' } },
+ { a: { level: ServiceStatusLevels.available, summary: 'a available' } },
+ ]);
+ });
+
+ it('emits an unavailable status if first emission times out, then continues future emissions', async () => {
+ jest.useFakeTimers();
+ const service = new PluginsStatusService({
+ core$: coreAllAvailable$,
+ pluginDependencies: new Map([
+ ['a', []],
+ ['b', ['a']],
+ ]),
+ });
+
+ const pluginA$ = new ReplaySubject(1);
+ service.set('a', pluginA$);
+ const firstEmission = service.getAll$().pipe(first()).toPromise();
+ jest.runAllTimers();
+
+ expect(await firstEmission).toEqual({
+ a: { level: ServiceStatusLevels.unavailable, summary: 'Status check timed out after 30s' },
+ b: {
+ level: ServiceStatusLevels.unavailable,
+ summary: '[a]: Status check timed out after 30s',
+ detail: 'See the status page for more information',
+ meta: {
+ affectedServices: {
+ a: {
+ level: ServiceStatusLevels.unavailable,
+ summary: 'Status check timed out after 30s',
+ },
+ },
+ },
+ },
+ });
+
+ pluginA$.next({ level: ServiceStatusLevels.available, summary: 'a available' });
+ const secondEmission = service.getAll$().pipe(first()).toPromise();
+ jest.runAllTimers();
+ expect(await secondEmission).toEqual({
+ a: { level: ServiceStatusLevels.available, summary: 'a available' },
+ b: { level: ServiceStatusLevels.available, summary: 'All dependencies are available' },
+ });
+ jest.useRealTimers();
+ });
});
describe('getDependenciesStatus$', () => {
diff --git a/src/core/server/status/plugins_status.ts b/src/core/server/status/plugins_status.ts
index 1aacbf3be56db..6a8ef1081e165 100644
--- a/src/core/server/status/plugins_status.ts
+++ b/src/core/server/status/plugins_status.ts
@@ -7,13 +7,22 @@
*/
import { BehaviorSubject, Observable, combineLatest, of } from 'rxjs';
-import { map, distinctUntilChanged, switchMap, debounceTime } from 'rxjs/operators';
+import {
+ map,
+ distinctUntilChanged,
+ switchMap,
+ debounceTime,
+ timeoutWith,
+ startWith,
+} from 'rxjs/operators';
import { isDeepStrictEqual } from 'util';
import { PluginName } from '../plugins';
-import { ServiceStatus, CoreStatus } from './types';
+import { ServiceStatus, CoreStatus, ServiceStatusLevels } from './types';
import { getSummaryStatus } from './get_summary_status';
+const STATUS_TIMEOUT_MS = 30 * 1000; // 30 seconds
+
interface Deps {
core$: Observable;
pluginDependencies: ReadonlyMap;
@@ -23,6 +32,7 @@ export class PluginsStatusService {
private readonly pluginStatuses = new Map>();
private readonly update$ = new BehaviorSubject(true);
private readonly defaultInheritedStatus$: Observable;
+ private newRegistrationsAllowed = true;
constructor(private readonly deps: Deps) {
this.defaultInheritedStatus$ = this.deps.core$.pipe(
@@ -35,10 +45,19 @@ export class PluginsStatusService {
}
public set(plugin: PluginName, status$: Observable) {
+ if (!this.newRegistrationsAllowed) {
+ throw new Error(
+ `Custom statuses cannot be registered after setup, plugin [${plugin}] attempted`
+ );
+ }
this.pluginStatuses.set(plugin, status$);
this.update$.next(true); // trigger all existing Observables to update from the new source Observable
}
+ public blockNewRegistrations() {
+ this.newRegistrationsAllowed = false;
+ }
+
public getAll$(): Observable> {
return this.getPluginStatuses$([...this.deps.pluginDependencies.keys()]);
}
@@ -86,13 +105,22 @@ export class PluginsStatusService {
return this.update$.pipe(
switchMap(() => {
const pluginStatuses = plugins
- .map(
- (depName) =>
- [depName, this.pluginStatuses.get(depName) ?? this.getDerivedStatus$(depName)] as [
- PluginName,
- Observable
- ]
- )
+ .map((depName) => {
+ const pluginStatus = this.pluginStatuses.get(depName)
+ ? this.pluginStatuses.get(depName)!.pipe(
+ timeoutWith(
+ STATUS_TIMEOUT_MS,
+ this.pluginStatuses.get(depName)!.pipe(
+ startWith({
+ level: ServiceStatusLevels.unavailable,
+ summary: `Status check timed out after ${STATUS_TIMEOUT_MS / 1000}s`,
+ })
+ )
+ )
+ )
+ : this.getDerivedStatus$(depName);
+ return [depName, pluginStatus] as [PluginName, Observable];
+ })
.map(([pName, status$]) =>
status$.pipe(map((status) => [pName, status] as [PluginName, ServiceStatus]))
);
diff --git a/src/core/server/status/status_service.ts b/src/core/server/status/status_service.ts
index b8c19508a5d61..d4dc8ed3d4d72 100644
--- a/src/core/server/status/status_service.ts
+++ b/src/core/server/status/status_service.ts
@@ -135,9 +135,11 @@ export class StatusService implements CoreService {
}
public start() {
- if (!this.overall$) {
- throw new Error('cannot call `start` before `setup`');
+ if (!this.pluginsStatus || !this.overall$) {
+ throw new Error(`StatusService#setup must be called before #start`);
}
+ this.pluginsStatus.blockNewRegistrations();
+
getOverallStatusChanges(this.overall$, this.stop$).subscribe((message) => {
this.logger.info(message);
});
diff --git a/src/core/server/status/types.ts b/src/core/server/status/types.ts
index 411b942c8eb33..bfca4c74d9365 100644
--- a/src/core/server/status/types.ts
+++ b/src/core/server/status/types.ts
@@ -196,6 +196,9 @@ export interface StatusServiceSetup {
* Completely overrides the default inherited status.
*
* @remarks
+ * The first emission from this Observable should occur within 30s, else this plugin's status will fallback to
+ * `unavailable` until the first emission.
+ *
* See the {@link StatusServiceSetup.derivedStatus$} API for leveraging the default status
* calculation that is provided by Core.
*/
diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
index 9ea6e8960e373..a224793bace3f 100755
--- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
+++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
@@ -31,6 +31,17 @@ kibana_vars=(
csp.rules
csp.strict
csp.warnLegacyBrowsers
+ csp.script_src
+ csp.worker_src
+ csp.style_src
+ csp.connect_src
+ csp.default_src
+ csp.font_src
+ csp.frame_src
+ csp.img_src
+ csp.frame_ancestors
+ csp.report_uri
+ csp.report_to
data.autocomplete.valueSuggestions.terminateAfter
data.autocomplete.valueSuggestions.timeout
elasticsearch.customHeaders
@@ -203,8 +214,8 @@ kibana_vars=(
xpack.actions.proxyUrl
xpack.actions.rejectUnauthorized
xpack.actions.responseTimeout
- xpack.actions.tls.proxyVerificationMode
- xpack.actions.tls.verificationMode
+ xpack.actions.ssl.proxyVerificationMode
+ xpack.actions.ssl.verificationMode
xpack.alerting.healthCheck.interval
xpack.alerting.invalidateApiKeysTask.interval
xpack.alerting.invalidateApiKeysTask.removalDelay
@@ -379,7 +390,8 @@ kibana_vars=(
xpack.task_manager.monitored_aggregated_stats_refresh_rate
xpack.task_manager.monitored_stats_required_freshness
xpack.task_manager.monitored_stats_running_average_window
- xpack.task_manager.monitored_stats_warn_delayed_task_start_in_seconds
+ xpack.task_manager.monitored_stats_health_verbose_log.enabled
+ xpack.task_manager.monitored_stats_health_verbose_log.warn_delayed_task_start_in_seconds
xpack.task_manager.monitored_task_execution_thresholds
xpack.task_manager.poll_interval
xpack.task_manager.request_capacity
diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts
index f372cf052d368..2c54bb8dba179 100644
--- a/src/dev/typescript/projects.ts
+++ b/src/dev/typescript/projects.ts
@@ -58,6 +58,9 @@ export const PROJECTS = [
...glob
.sync('test/interpreter_functional/plugins/*/tsconfig.json', { cwd: REPO_ROOT })
.map((path) => new Project(resolve(REPO_ROOT, path))),
+ ...glob
+ .sync('test/server_integration/__fixtures__/plugins/*/tsconfig.json', { cwd: REPO_ROOT })
+ .map((path) => new Project(resolve(REPO_ROOT, path))),
];
export function filterProjectsByFlag(projectFlag?: string) {
diff --git a/src/plugins/console/public/application/components/welcome_panel.tsx b/src/plugins/console/public/application/components/welcome_panel.tsx
index eb746e313d228..8514d41c04a51 100644
--- a/src/plugins/console/public/application/components/welcome_panel.tsx
+++ b/src/plugins/console/public/application/components/welcome_panel.tsx
@@ -27,7 +27,7 @@ interface Props {
export function WelcomePanel(props: Props) {
return (
-
+
diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
index 9f56740fdac22..afe339f3f43a2 100644
--- a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
+++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
@@ -603,7 +603,7 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = `
}
>
-
-
+
@@ -950,7 +950,7 @@ exports[`DashboardEmptyScreen renders correctly with view mode 1`] = `
}
>
-
-
+
diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts
index c584b44286e07..ff7708689c221 100644
--- a/src/plugins/dashboard/public/index.ts
+++ b/src/plugins/dashboard/public/index.ts
@@ -22,11 +22,14 @@ export {
DashboardUrlGenerator,
DashboardFeatureFlagConfig,
} from './plugin';
+
export {
DASHBOARD_APP_URL_GENERATOR,
createDashboardUrlGenerator,
DashboardUrlGeneratorState,
} from './url_generator';
+export { DashboardAppLocator, DashboardAppLocatorParams } from './locator';
+
export { DashboardSavedObject } from './saved_dashboards';
export { SavedDashboardPanel, DashboardContainerInput } from './types';
diff --git a/src/plugins/dashboard/public/locator.test.ts b/src/plugins/dashboard/public/locator.test.ts
new file mode 100644
index 0000000000000..0b647ac00ce31
--- /dev/null
+++ b/src/plugins/dashboard/public/locator.test.ts
@@ -0,0 +1,323 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { DashboardAppLocatorDefinition } from './locator';
+import { hashedItemStore } from '../../kibana_utils/public';
+import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock';
+import { esFilters } from '../../data/public';
+
+describe('dashboard locator', () => {
+ beforeEach(() => {
+ // @ts-ignore
+ hashedItemStore.storage = mockStorage;
+ });
+
+ test('creates a link to a saved dashboard', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => [],
+ });
+ const location = await definition.getLocation({});
+
+ expect(location).toMatchObject({
+ app: 'dashboards',
+ path: '#/create?_a=()&_g=()',
+ state: {},
+ });
+ });
+
+ test('creates a link with global time range set up', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => [],
+ });
+ const location = await definition.getLocation({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ });
+
+ expect(location).toMatchObject({
+ app: 'dashboards',
+ path: '#/create?_a=()&_g=(time:(from:now-15m,mode:relative,to:now))',
+ state: {},
+ });
+ });
+
+ test('creates a link with filters, time range, refresh interval and query to a saved object', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => [],
+ });
+ const location = await definition.getLocation({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ refreshInterval: { pause: false, value: 300 },
+ dashboardId: '123',
+ filters: [
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'hi' },
+ },
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'hi' },
+ $state: {
+ store: esFilters.FilterStateStore.GLOBAL_STATE,
+ },
+ },
+ ],
+ query: { query: 'bye', language: 'kuery' },
+ });
+
+ expect(location).toMatchObject({
+ app: 'dashboards',
+ path: `#/view/123?_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),query:(language:kuery,query:bye))&_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))`,
+ state: {},
+ });
+ });
+
+ test('searchSessionId', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => [],
+ });
+ const location = await definition.getLocation({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ refreshInterval: { pause: false, value: 300 },
+ dashboardId: '123',
+ filters: [],
+ query: { query: 'bye', language: 'kuery' },
+ searchSessionId: '__sessionSearchId__',
+ });
+
+ expect(location).toMatchObject({
+ app: 'dashboards',
+ path: `#/view/123?_a=(filters:!(),query:(language:kuery,query:bye))&_g=(filters:!(),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&searchSessionId=__sessionSearchId__`,
+ state: {},
+ });
+ });
+
+ test('savedQuery', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => [],
+ });
+ const location = await definition.getLocation({
+ savedQuery: '__savedQueryId__',
+ });
+
+ expect(location).toMatchObject({
+ app: 'dashboards',
+ path: `#/create?_a=(savedQuery:__savedQueryId__)&_g=()`,
+ state: {},
+ });
+ expect(location.path).toContain('__savedQueryId__');
+ });
+
+ test('panels', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => [],
+ });
+ const location = await definition.getLocation({
+ panels: [{ fakePanelContent: 'fakePanelContent' }] as any,
+ });
+
+ expect(location).toMatchObject({
+ app: 'dashboards',
+ path: `#/create?_a=(panels:!((fakePanelContent:fakePanelContent)))&_g=()`,
+ state: {},
+ });
+ });
+
+ test('if no useHash setting is given, uses the one was start services', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: true,
+ getDashboardFilterFields: async (dashboardId: string) => [],
+ });
+ const location = await definition.getLocation({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ });
+
+ expect(location.path.indexOf('relative')).toBe(-1);
+ });
+
+ test('can override a false useHash ui setting', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => [],
+ });
+ const location = await definition.getLocation({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ useHash: true,
+ });
+
+ expect(location.path.indexOf('relative')).toBe(-1);
+ });
+
+ test('can override a true useHash ui setting', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: true,
+ getDashboardFilterFields: async (dashboardId: string) => [],
+ });
+ const location = await definition.getLocation({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ useHash: false,
+ });
+
+ expect(location.path.indexOf('relative')).toBeGreaterThan(1);
+ });
+
+ describe('preserving saved filters', () => {
+ const savedFilter1 = {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'savedfilter1' },
+ };
+
+ const savedFilter2 = {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'savedfilter2' },
+ };
+
+ const appliedFilter = {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'appliedfilter' },
+ };
+
+ test('attaches filters from destination dashboard', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => {
+ return dashboardId === 'dashboard1'
+ ? [savedFilter1]
+ : dashboardId === 'dashboard2'
+ ? [savedFilter2]
+ : [];
+ },
+ });
+
+ const location1 = await definition.getLocation({
+ dashboardId: 'dashboard1',
+ filters: [appliedFilter],
+ });
+
+ expect(location1.path).toEqual(expect.stringContaining('query:savedfilter1'));
+ expect(location1.path).toEqual(expect.stringContaining('query:appliedfilter'));
+
+ const location2 = await definition.getLocation({
+ dashboardId: 'dashboard2',
+ filters: [appliedFilter],
+ });
+
+ expect(location2.path).toEqual(expect.stringContaining('query:savedfilter2'));
+ expect(location2.path).toEqual(expect.stringContaining('query:appliedfilter'));
+ });
+
+ test("doesn't fail if can't retrieve filters from destination dashboard", async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => {
+ if (dashboardId === 'dashboard1') {
+ throw new Error('Not found');
+ }
+ return [];
+ },
+ });
+
+ const location = await definition.getLocation({
+ dashboardId: 'dashboard1',
+ filters: [appliedFilter],
+ });
+
+ expect(location.path).not.toEqual(expect.stringContaining('query:savedfilter1'));
+ expect(location.path).toEqual(expect.stringContaining('query:appliedfilter'));
+ });
+
+ test('can enforce empty filters', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => {
+ if (dashboardId === 'dashboard1') {
+ return [savedFilter1];
+ }
+ return [];
+ },
+ });
+
+ const location = await definition.getLocation({
+ dashboardId: 'dashboard1',
+ filters: [],
+ preserveSavedFilters: false,
+ });
+
+ expect(location.path).not.toEqual(expect.stringContaining('query:savedfilter1'));
+ expect(location.path).not.toEqual(expect.stringContaining('query:appliedfilter'));
+ expect(location.path).toMatchInlineSnapshot(
+ `"#/view/dashboard1?_a=(filters:!())&_g=(filters:!())"`
+ );
+ });
+
+ test('no filters in result url if no filters applied', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => {
+ if (dashboardId === 'dashboard1') {
+ return [savedFilter1];
+ }
+ return [];
+ },
+ });
+
+ const location = await definition.getLocation({
+ dashboardId: 'dashboard1',
+ });
+
+ expect(location.path).not.toEqual(expect.stringContaining('filters'));
+ expect(location.path).toMatchInlineSnapshot(`"#/view/dashboard1?_a=()&_g=()"`);
+ });
+
+ test('can turn off preserving filters', async () => {
+ const definition = new DashboardAppLocatorDefinition({
+ useHashedUrl: false,
+ getDashboardFilterFields: async (dashboardId: string) => {
+ if (dashboardId === 'dashboard1') {
+ return [savedFilter1];
+ }
+ return [];
+ },
+ });
+
+ const location = await definition.getLocation({
+ dashboardId: 'dashboard1',
+ filters: [appliedFilter],
+ preserveSavedFilters: false,
+ });
+
+ expect(location.path).not.toEqual(expect.stringContaining('query:savedfilter1'));
+ expect(location.path).toEqual(expect.stringContaining('query:appliedfilter'));
+ });
+ });
+});
diff --git a/src/plugins/dashboard/public/locator.ts b/src/plugins/dashboard/public/locator.ts
new file mode 100644
index 0000000000000..e154351819ee9
--- /dev/null
+++ b/src/plugins/dashboard/public/locator.ts
@@ -0,0 +1,160 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { SerializableState } from 'src/plugins/kibana_utils/common';
+import type { TimeRange, Filter, Query, QueryState, RefreshInterval } from '../../data/public';
+import type { LocatorDefinition, LocatorPublic } from '../../share/public';
+import type { SavedDashboardPanel } from '../common/types';
+import { esFilters } from '../../data/public';
+import { setStateToKbnUrl } from '../../kibana_utils/public';
+import { ViewMode } from '../../embeddable/public';
+import { DashboardConstants } from './dashboard_constants';
+
+const cleanEmptyKeys = (stateObj: Record) => {
+ Object.keys(stateObj).forEach((key) => {
+ if (stateObj[key] === undefined) {
+ delete stateObj[key];
+ }
+ });
+ return stateObj;
+};
+
+export const DASHBOARD_APP_LOCATOR = 'DASHBOARD_APP_LOCATOR';
+
+export interface DashboardAppLocatorParams extends SerializableState {
+ /**
+ * If given, the dashboard saved object with this id will be loaded. If not given,
+ * a new, unsaved dashboard will be loaded up.
+ */
+ dashboardId?: string;
+ /**
+ * Optionally set the time range in the time picker.
+ */
+ timeRange?: TimeRange;
+
+ /**
+ * Optionally set the refresh interval.
+ */
+ refreshInterval?: RefreshInterval & SerializableState;
+
+ /**
+ * Optionally apply filers. NOTE: if given and used in conjunction with `dashboardId`, and the
+ * saved dashboard has filters saved with it, this will _replace_ those filters.
+ */
+ filters?: Filter[];
+ /**
+ * Optionally set a query. NOTE: if given and used in conjunction with `dashboardId`, and the
+ * saved dashboard has a query saved with it, this will _replace_ that query.
+ */
+ query?: Query;
+ /**
+ * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines
+ * whether to hash the data in the url to avoid url length issues.
+ */
+ useHash?: boolean;
+
+ /**
+ * When `true` filters from saved filters from destination dashboard as merged with applied filters
+ * When `false` applied filters take precedence and override saved filters
+ *
+ * true is default
+ */
+ preserveSavedFilters?: boolean;
+
+ /**
+ * View mode of the dashboard.
+ */
+ viewMode?: ViewMode;
+
+ /**
+ * Search search session ID to restore.
+ * (Background search)
+ */
+ searchSessionId?: string;
+
+ /**
+ * List of dashboard panels
+ */
+ panels?: SavedDashboardPanel[] & SerializableState;
+
+ /**
+ * Saved query ID
+ */
+ savedQuery?: string;
+}
+
+export type DashboardAppLocator = LocatorPublic;
+
+export interface DashboardAppLocatorDependencies {
+ useHashedUrl: boolean;
+ getDashboardFilterFields: (dashboardId: string) => Promise;
+}
+
+export class DashboardAppLocatorDefinition implements LocatorDefinition {
+ public readonly id = DASHBOARD_APP_LOCATOR;
+
+ constructor(protected readonly deps: DashboardAppLocatorDependencies) {}
+
+ public readonly getLocation = async (params: DashboardAppLocatorParams) => {
+ const useHash = params.useHash ?? this.deps.useHashedUrl;
+ const hash = params.dashboardId ? `view/${params.dashboardId}` : `create`;
+
+ const getSavedFiltersFromDestinationDashboardIfNeeded = async (): Promise => {
+ if (params.preserveSavedFilters === false) return [];
+ if (!params.dashboardId) return [];
+ try {
+ return await this.deps.getDashboardFilterFields(params.dashboardId);
+ } catch (e) {
+ // In case dashboard is missing, build the url without those filters.
+ // The Dashboard app will handle redirect to landing page with a toast message.
+ return [];
+ }
+ };
+
+ // leave filters `undefined` if no filters was applied
+ // in this case dashboard will restore saved filters on its own
+ const filters = params.filters && [
+ ...(await getSavedFiltersFromDestinationDashboardIfNeeded()),
+ ...params.filters,
+ ];
+
+ let path = setStateToKbnUrl(
+ '_a',
+ cleanEmptyKeys({
+ query: params.query,
+ filters: filters?.filter((f) => !esFilters.isFilterPinned(f)),
+ viewMode: params.viewMode,
+ panels: params.panels,
+ savedQuery: params.savedQuery,
+ }),
+ { useHash },
+ `#/${hash}`
+ );
+
+ path = setStateToKbnUrl(
+ '_g',
+ cleanEmptyKeys({
+ time: params.timeRange,
+ filters: filters?.filter((f) => esFilters.isFilterPinned(f)),
+ refreshInterval: params.refreshInterval,
+ }),
+ { useHash },
+ path
+ );
+
+ if (params.searchSessionId) {
+ path = `${path}&${DashboardConstants.SEARCH_SESSION_ID}=${params.searchSessionId}`;
+ }
+
+ return {
+ app: DashboardConstants.DASHBOARDS_ID,
+ path,
+ state: {},
+ };
+ };
+}
diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx
index b5d6eda71ca4a..53a8e90a8c35c 100644
--- a/src/plugins/dashboard/public/plugin.tsx
+++ b/src/plugins/dashboard/public/plugin.tsx
@@ -72,6 +72,7 @@ import {
DASHBOARD_APP_URL_GENERATOR,
DashboardUrlGeneratorState,
} from './url_generator';
+import { DashboardAppLocatorDefinition, DashboardAppLocator } from './locator';
import { createSavedDashboardLoader } from './saved_dashboards';
import { DashboardConstants } from './dashboard_constants';
import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder';
@@ -121,14 +122,25 @@ export interface DashboardStartDependencies {
visualizations: VisualizationsStart;
}
-export type DashboardSetup = void;
+export interface DashboardSetup {
+ locator?: DashboardAppLocator;
+}
export interface DashboardStart {
getSavedDashboardLoader: () => SavedObjectLoader;
getDashboardContainerByValueRenderer: () => ReturnType<
typeof createDashboardContainerByValueRenderer
>;
+ /**
+ * @deprecated Use dashboard locator instead. Dashboard locator is available
+ * under `.locator` key. This dashboard URL generator will be removed soon.
+ *
+ * ```ts
+ * plugins.dashboard.locator.getLocation({ ... });
+ * ```
+ */
dashboardUrlGenerator?: DashboardUrlGenerator;
+ locator?: DashboardAppLocator;
dashboardFeatureFlagConfig: DashboardFeatureFlagConfig;
}
@@ -142,7 +154,11 @@ export class DashboardPlugin
private currentHistory: ScopedHistory | undefined = undefined;
private dashboardFeatureFlagConfig?: DashboardFeatureFlagConfig;
+ /**
+ * @deprecated Use locator instead.
+ */
private dashboardUrlGenerator?: DashboardUrlGenerator;
+ private locator?: DashboardAppLocator;
public setup(
core: CoreSetup,
@@ -205,6 +221,19 @@ export class DashboardPlugin
};
};
+ if (share) {
+ this.locator = share.url.locators.create(
+ new DashboardAppLocatorDefinition({
+ useHashedUrl: core.uiSettings.get('state:storeInSessionStorage'),
+ getDashboardFilterFields: async (dashboardId: string) => {
+ const [, , selfStart] = await core.getStartServices();
+ const dashboard = await selfStart.getSavedDashboardLoader().get(dashboardId);
+ return dashboard?.searchSource?.getField('filter') ?? [];
+ },
+ })
+ );
+ }
+
const {
appMounted,
appUnMounted,
@@ -333,6 +362,10 @@ export class DashboardPlugin
order: 100,
});
}
+
+ return {
+ locator: this.locator,
+ };
}
public start(core: CoreStart, plugins: DashboardStartDependencies): DashboardStart {
@@ -417,6 +450,7 @@ export class DashboardPlugin
});
},
dashboardUrlGenerator: this.dashboardUrlGenerator,
+ locator: this.locator,
dashboardFeatureFlagConfig: this.dashboardFeatureFlagConfig!,
};
}
diff --git a/src/plugins/dashboard/public/url_generator.ts b/src/plugins/dashboard/public/url_generator.ts
index 58036ef70fa4a..5c0cd32ee5a16 100644
--- a/src/plugins/dashboard/public/url_generator.ts
+++ b/src/plugins/dashboard/public/url_generator.ts
@@ -26,6 +26,9 @@ export const GLOBAL_STATE_STORAGE_KEY = '_g';
export const DASHBOARD_APP_URL_GENERATOR = 'DASHBOARD_APP_URL_GENERATOR';
+/**
+ * @deprecated Use dashboard locator instead.
+ */
export interface DashboardUrlGeneratorState {
/**
* If given, the dashboard saved object with this id will be loaded. If not given,
@@ -88,6 +91,9 @@ export interface DashboardUrlGeneratorState {
savedQuery?: string;
}
+/**
+ * @deprecated Use dashboard locator instead.
+ */
export const createDashboardUrlGenerator = (
getStartServices: () => Promise<{
appBasePath: string;
diff --git a/src/plugins/data/common/es_query/es_query/build_es_query.ts b/src/plugins/data/common/es_query/es_query/build_es_query.ts
index 45724796c3518..d7b3c630d1a6e 100644
--- a/src/plugins/data/common/es_query/es_query/build_es_query.ts
+++ b/src/plugins/data/common/es_query/es_query/build_es_query.ts
@@ -10,9 +10,9 @@ import { groupBy, has, isEqual } from 'lodash';
import { buildQueryFromKuery } from './from_kuery';
import { buildQueryFromFilters } from './from_filters';
import { buildQueryFromLucene } from './from_lucene';
-import { IIndexPattern } from '../../index_patterns';
import { Filter } from '../filters';
import { Query } from '../../query/types';
+import { IndexPatternBase } from './types';
export interface EsQueryConfig {
allowLeadingWildcards: boolean;
@@ -36,7 +36,7 @@ function removeMatchAll(filters: T[]) {
* config contains dateformat:tz
*/
export function buildEsQuery(
- indexPattern: IIndexPattern | undefined,
+ indexPattern: IndexPatternBase | undefined,
queries: Query | Query[],
filters: Filter | Filter[],
config: EsQueryConfig = {
diff --git a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts
index 478263d5ce601..b376436756092 100644
--- a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts
+++ b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts
@@ -6,15 +6,16 @@
* Side Public License, v 1.
*/
-import { IIndexPattern, IFieldType } from '../../index_patterns';
+import { IFieldType } from '../../index_patterns';
import { Filter } from '../filters';
+import { IndexPatternBase } from './types';
/*
* TODO: We should base this on something better than `filter.meta.key`. We should probably modify
* this to check if `filter.meta.index` matches `indexPattern.id` instead, but that's a breaking
* change.
*/
-export function filterMatchesIndex(filter: Filter, indexPattern?: IIndexPattern | null) {
+export function filterMatchesIndex(filter: Filter, indexPattern?: IndexPatternBase | null) {
if (!filter.meta?.key || !indexPattern) {
return true;
}
diff --git a/src/plugins/data/common/es_query/es_query/from_filters.ts b/src/plugins/data/common/es_query/es_query/from_filters.ts
index e50862235af1d..7b3c58d45a569 100644
--- a/src/plugins/data/common/es_query/es_query/from_filters.ts
+++ b/src/plugins/data/common/es_query/es_query/from_filters.ts
@@ -10,7 +10,7 @@ import { isUndefined } from 'lodash';
import { migrateFilter } from './migrate_filter';
import { filterMatchesIndex } from './filter_matches_index';
import { Filter, cleanFilter, isFilterDisabled } from '../filters';
-import { IIndexPattern } from '../../index_patterns';
+import { IndexPatternBase } from './types';
import { handleNestedFilter } from './handle_nested_filter';
/**
@@ -45,7 +45,7 @@ const translateToQuery = (filter: Filter) => {
export const buildQueryFromFilters = (
filters: Filter[] = [],
- indexPattern: IIndexPattern | undefined,
+ indexPattern: IndexPatternBase | undefined,
ignoreFilterIfFieldNotInIndex: boolean = false
) => {
filters = filters.filter((filter) => filter && !isFilterDisabled(filter));
diff --git a/src/plugins/data/common/es_query/es_query/from_kuery.ts b/src/plugins/data/common/es_query/es_query/from_kuery.ts
index afedaae45872b..3eccfd8776113 100644
--- a/src/plugins/data/common/es_query/es_query/from_kuery.ts
+++ b/src/plugins/data/common/es_query/es_query/from_kuery.ts
@@ -7,11 +7,11 @@
*/
import { fromKueryExpression, toElasticsearchQuery, nodeTypes, KueryNode } from '../kuery';
-import { IIndexPattern } from '../../index_patterns';
+import { IndexPatternBase } from './types';
import { Query } from '../../query/types';
export function buildQueryFromKuery(
- indexPattern: IIndexPattern | undefined,
+ indexPattern: IndexPatternBase | undefined,
queries: Query[] = [],
allowLeadingWildcards: boolean = false,
dateFormatTZ?: string
@@ -24,7 +24,7 @@ export function buildQueryFromKuery(
}
function buildQuery(
- indexPattern: IIndexPattern | undefined,
+ indexPattern: IndexPatternBase | undefined,
queryASTs: KueryNode[],
config: Record = {}
) {
diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts
index ee5305132042a..d312d034df564 100644
--- a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts
+++ b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts
@@ -9,13 +9,14 @@
import { handleNestedFilter } from './handle_nested_filter';
import { fields } from '../../index_patterns/mocks';
import { buildPhraseFilter, buildQueryFilter } from '../filters';
-import { IFieldType, IIndexPattern } from '../../index_patterns';
+import { IndexPatternBase } from './types';
+import { IFieldType } from '../../index_patterns';
describe('handleNestedFilter', function () {
- const indexPattern: IIndexPattern = ({
+ const indexPattern: IndexPatternBase = {
id: 'logstash-*',
fields,
- } as unknown) as IIndexPattern;
+ };
it("should return the filter's query wrapped in nested query if the target field is nested", () => {
const field = getField('nestedField.child');
diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts b/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts
index 93927d81565ef..60e92769503fb 100644
--- a/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts
+++ b/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts
@@ -7,9 +7,9 @@
*/
import { getFilterField, cleanFilter, Filter } from '../filters';
-import { IIndexPattern } from '../../index_patterns';
+import { IndexPatternBase } from './types';
-export const handleNestedFilter = (filter: Filter, indexPattern?: IIndexPattern) => {
+export const handleNestedFilter = (filter: Filter, indexPattern?: IndexPatternBase) => {
if (!indexPattern) return filter;
const fieldName = getFilterField(filter);
diff --git a/src/plugins/data/common/es_query/es_query/index.ts b/src/plugins/data/common/es_query/es_query/index.ts
index 31529480c8ac9..c10ea5846ae3f 100644
--- a/src/plugins/data/common/es_query/es_query/index.ts
+++ b/src/plugins/data/common/es_query/es_query/index.ts
@@ -11,3 +11,4 @@ export { buildQueryFromFilters } from './from_filters';
export { luceneStringToDsl } from './lucene_string_to_dsl';
export { decorateQuery } from './decorate_query';
export { getEsQueryConfig } from './get_es_query_config';
+export { IndexPatternBase } from './types';
diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.ts
index c7c44d019a31c..9bd78b092fc18 100644
--- a/src/plugins/data/common/es_query/es_query/migrate_filter.ts
+++ b/src/plugins/data/common/es_query/es_query/migrate_filter.ts
@@ -9,7 +9,7 @@
import { get, omit } from 'lodash';
import { getConvertedValueForField } from '../filters';
import { Filter } from '../filters';
-import { IIndexPattern } from '../../index_patterns';
+import { IndexPatternBase } from './types';
export interface DeprecatedMatchPhraseFilter extends Filter {
query: {
@@ -28,7 +28,7 @@ function isDeprecatedMatchPhraseFilter(filter: any): filter is DeprecatedMatchPh
return Boolean(fieldName && get(filter, ['query', 'match', fieldName, 'type']) === 'phrase');
}
-export function migrateFilter(filter: Filter, indexPattern?: IIndexPattern) {
+export function migrateFilter(filter: Filter, indexPattern?: IndexPatternBase) {
if (isDeprecatedMatchPhraseFilter(filter)) {
const fieldName = Object.keys(filter.query.match)[0];
const params: Record = get(filter, ['query', 'match', fieldName]);
diff --git a/src/plugins/data/common/es_query/es_query/types.ts b/src/plugins/data/common/es_query/es_query/types.ts
new file mode 100644
index 0000000000000..2133736516049
--- /dev/null
+++ b/src/plugins/data/common/es_query/es_query/types.ts
@@ -0,0 +1,14 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { IFieldType } from '../../index_patterns';
+
+export interface IndexPatternBase {
+ fields: IFieldType[];
+ id?: string;
+}
diff --git a/src/plugins/data/common/es_query/filters/build_filters.ts b/src/plugins/data/common/es_query/filters/build_filters.ts
index ba1bd0a615493..369f9530fb92b 100644
--- a/src/plugins/data/common/es_query/filters/build_filters.ts
+++ b/src/plugins/data/common/es_query/filters/build_filters.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { IIndexPattern, IFieldType } from '../..';
+import { IFieldType, IndexPatternBase } from '../..';
import {
Filter,
FILTERS,
@@ -19,7 +19,7 @@ import {
} from '.';
export function buildFilter(
- indexPattern: IIndexPattern,
+ indexPattern: IndexPatternBase,
field: IFieldType,
type: FILTERS,
negate: boolean,
@@ -59,7 +59,7 @@ export function buildCustomFilter(
}
function buildBaseFilter(
- indexPattern: IIndexPattern,
+ indexPattern: IndexPatternBase,
field: IFieldType,
type: FILTERS,
params: any
diff --git a/src/plugins/data/common/es_query/filters/exists_filter.ts b/src/plugins/data/common/es_query/filters/exists_filter.ts
index 441a6bcb924b7..4836950c3bb27 100644
--- a/src/plugins/data/common/es_query/filters/exists_filter.ts
+++ b/src/plugins/data/common/es_query/filters/exists_filter.ts
@@ -7,7 +7,8 @@
*/
import { Filter, FilterMeta } from './meta_filter';
-import { IIndexPattern, IFieldType } from '../../index_patterns';
+import { IFieldType } from '../../index_patterns';
+import { IndexPatternBase } from '..';
export type ExistsFilterMeta = FilterMeta;
@@ -26,7 +27,7 @@ export const getExistsFilterField = (filter: ExistsFilter) => {
return filter.exists && filter.exists.field;
};
-export const buildExistsFilter = (field: IFieldType, indexPattern: IIndexPattern) => {
+export const buildExistsFilter = (field: IFieldType, indexPattern: IndexPatternBase) => {
return {
meta: {
index: indexPattern.id,
diff --git a/src/plugins/data/common/es_query/filters/index.ts b/src/plugins/data/common/es_query/filters/index.ts
index 133f5cd232e6f..fe7cdadabaee3 100644
--- a/src/plugins/data/common/es_query/filters/index.ts
+++ b/src/plugins/data/common/es_query/filters/index.ts
@@ -14,10 +14,8 @@ export * from './custom_filter';
export * from './exists_filter';
export * from './geo_bounding_box_filter';
export * from './geo_polygon_filter';
-export * from './get_display_value';
export * from './get_filter_field';
export * from './get_filter_params';
-export * from './get_index_pattern_from_filter';
export * from './match_all_filter';
export * from './meta_filter';
export * from './missing_filter';
diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.ts b/src/plugins/data/common/es_query/filters/phrase_filter.ts
index 85562435e68d0..27c1e85562097 100644
--- a/src/plugins/data/common/es_query/filters/phrase_filter.ts
+++ b/src/plugins/data/common/es_query/filters/phrase_filter.ts
@@ -8,7 +8,8 @@
import type { estypes } from '@elastic/elasticsearch';
import { get, isPlainObject } from 'lodash';
import { Filter, FilterMeta } from './meta_filter';
-import { IIndexPattern, IFieldType } from '../../index_patterns';
+import { IFieldType } from '../../index_patterns';
+import { IndexPatternBase } from '..';
export type PhraseFilterMeta = FilterMeta & {
params?: {
@@ -60,7 +61,7 @@ export const getPhraseFilterValue = (filter: PhraseFilter): PhraseFilterValue =>
export const buildPhraseFilter = (
field: IFieldType,
value: any,
- indexPattern: IIndexPattern
+ indexPattern: IndexPatternBase
): PhraseFilter => {
const convertedValue = getConvertedValueForField(field, value);
diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/src/plugins/data/common/es_query/filters/phrases_filter.ts
index 849c1b3faef2a..2694461fc1930 100644
--- a/src/plugins/data/common/es_query/filters/phrases_filter.ts
+++ b/src/plugins/data/common/es_query/filters/phrases_filter.ts
@@ -9,7 +9,8 @@
import { Filter, FilterMeta } from './meta_filter';
import { getPhraseScript } from './phrase_filter';
import { FILTERS } from './index';
-import { IIndexPattern, IFieldType } from '../../index_patterns';
+import { IFieldType } from '../../index_patterns';
+import { IndexPatternBase } from '../es_query';
export type PhrasesFilterMeta = FilterMeta & {
params: string[]; // The unformatted values
@@ -34,17 +35,12 @@ export const getPhrasesFilterField = (filter: PhrasesFilter) => {
export const buildPhrasesFilter = (
field: IFieldType,
params: any[],
- indexPattern: IIndexPattern
+ indexPattern: IndexPatternBase
) => {
const index = indexPattern.id;
const type = FILTERS.PHRASES;
const key = field.name;
- const format = (f: IFieldType, value: any) =>
- f && f.format && f.format.convert ? f.format.convert(value) : value;
-
- const value = params.map((v: any) => format(field, v)).join(', ');
-
let should;
if (field.scripted) {
should = params.map((v: any) => ({
@@ -59,7 +55,7 @@ export const buildPhrasesFilter = (
}
return {
- meta: { index, type, key, value, params },
+ meta: { index, type, key, params },
query: {
bool: {
should,
diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts
index a082b93c0a79a..9f1d9a5d08926 100644
--- a/src/plugins/data/common/es_query/filters/range_filter.ts
+++ b/src/plugins/data/common/es_query/filters/range_filter.ts
@@ -8,7 +8,8 @@
import type { estypes } from '@elastic/elasticsearch';
import { map, reduce, mapValues, get, keys, pickBy } from 'lodash';
import { Filter, FilterMeta } from './meta_filter';
-import { IIndexPattern, IFieldType } from '../../index_patterns';
+import { IFieldType } from '../../index_patterns';
+import { IndexPatternBase } from '..';
const OPERANDS_IN_RANGE = 2;
@@ -83,17 +84,14 @@ export const getRangeFilterField = (filter: RangeFilter) => {
};
const formatValue = (field: IFieldType, params: any[]) =>
- map(params, (val: any, key: string) => get(operators, key) + format(field, val)).join(' ');
-
-const format = (field: IFieldType, value: any) =>
- field && field.format && field.format.convert ? field.format.convert(value) : value;
+ map(params, (val: any, key: string) => get(operators, key) + val).join(' ');
// Creates a filter where the value for the given field is in the given range
// params should be an object containing `lt`, `lte`, `gt`, and/or `gte`
export const buildRangeFilter = (
field: IFieldType,
params: RangeFilterParams,
- indexPattern: IIndexPattern,
+ indexPattern: IndexPatternBase,
formattedValue?: string
): RangeFilter => {
const filter: any = { meta: { index: indexPattern.id, params: {} } };
diff --git a/src/plugins/data/common/es_query/kuery/ast/ast.ts b/src/plugins/data/common/es_query/kuery/ast/ast.ts
index be82128969968..3e7b25897cab7 100644
--- a/src/plugins/data/common/es_query/kuery/ast/ast.ts
+++ b/src/plugins/data/common/es_query/kuery/ast/ast.ts
@@ -10,10 +10,10 @@ import { JsonObject } from '@kbn/common-utils';
import { nodeTypes } from '../node_types/index';
import { KQLSyntaxError } from '../kuery_syntax_error';
import { KueryNode, DslQuery, KueryParseOptions } from '../types';
-import { IIndexPattern } from '../../../index_patterns/types';
// @ts-ignore
import { parse as parseKuery } from './_generated_/kuery';
+import { IndexPatternBase } from '../..';
const fromExpression = (
expression: string | DslQuery,
@@ -65,7 +65,7 @@ export const fromKueryExpression = (
*/
export const toElasticsearchQuery = (
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config?: Record,
context?: Record
): JsonObject => {
diff --git a/src/plugins/data/common/es_query/kuery/functions/and.ts b/src/plugins/data/common/es_query/kuery/functions/and.ts
index 1989704cb627e..ba7d5d1f6645b 100644
--- a/src/plugins/data/common/es_query/kuery/functions/and.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/and.ts
@@ -7,7 +7,7 @@
*/
import * as ast from '../ast';
-import { IIndexPattern, KueryNode } from '../../..';
+import { IndexPatternBase, KueryNode } from '../../..';
export function buildNodeParams(children: KueryNode[]) {
return {
@@ -17,7 +17,7 @@ export function buildNodeParams(children: KueryNode[]) {
export function toElasticsearchQuery(
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config: Record = {},
context: Record = {}
) {
diff --git a/src/plugins/data/common/es_query/kuery/functions/exists.ts b/src/plugins/data/common/es_query/kuery/functions/exists.ts
index 5238fb1d8ee7f..fa6c37e6ba18f 100644
--- a/src/plugins/data/common/es_query/kuery/functions/exists.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/exists.ts
@@ -8,7 +8,7 @@
import { get } from 'lodash';
import * as literal from '../node_types/literal';
-import { IIndexPattern, KueryNode, IFieldType } from '../../..';
+import { KueryNode, IFieldType, IndexPatternBase } from '../../..';
export function buildNodeParams(fieldName: string) {
return {
@@ -18,7 +18,7 @@ export function buildNodeParams(fieldName: string) {
export function toElasticsearchQuery(
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config: Record = {},
context: Record = {}
) {
diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts b/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts
index f2498f3ea2ad4..38a433b1b80ab 100644
--- a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts
@@ -9,7 +9,7 @@
import _ from 'lodash';
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
-import { IIndexPattern, KueryNode, IFieldType, LatLon } from '../../..';
+import { IndexPatternBase, KueryNode, IFieldType, LatLon } from '../../..';
export function buildNodeParams(fieldName: string, params: any) {
params = _.pick(params, 'topLeft', 'bottomRight');
@@ -26,7 +26,7 @@ export function buildNodeParams(fieldName: string, params: any) {
export function toElasticsearchQuery(
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config: Record = {},
context: Record = {}
) {
diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts
index 584a315930d9c..69de7248a7b38 100644
--- a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts
@@ -8,7 +8,7 @@
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
-import { IIndexPattern, KueryNode, IFieldType, LatLon } from '../../..';
+import { IndexPatternBase, KueryNode, IFieldType, LatLon } from '../../..';
import { LiteralTypeBuildNode } from '../node_types/types';
export function buildNodeParams(fieldName: string, points: LatLon[]) {
@@ -25,7 +25,7 @@ export function buildNodeParams(fieldName: string, points: LatLon[]) {
export function toElasticsearchQuery(
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config: Record = {},
context: Record = {}
) {
diff --git a/src/plugins/data/common/es_query/kuery/functions/is.ts b/src/plugins/data/common/es_query/kuery/functions/is.ts
index a18ad230c3cae..55d036c2156f9 100644
--- a/src/plugins/data/common/es_query/kuery/functions/is.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/is.ts
@@ -11,7 +11,7 @@ import { getPhraseScript } from '../../filters';
import { getFields } from './utils/get_fields';
import { getTimeZoneFromSettings } from '../../utils';
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
-import { IIndexPattern, KueryNode, IFieldType } from '../../..';
+import { IndexPatternBase, KueryNode, IFieldType } from '../../..';
import * as ast from '../ast';
@@ -39,7 +39,7 @@ export function buildNodeParams(fieldName: string, value: any, isPhrase: boolean
export function toElasticsearchQuery(
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config: Record = {},
context: Record = {}
) {
diff --git a/src/plugins/data/common/es_query/kuery/functions/nested.ts b/src/plugins/data/common/es_query/kuery/functions/nested.ts
index bfd01ef39764c..46ceeaf3e5de6 100644
--- a/src/plugins/data/common/es_query/kuery/functions/nested.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/nested.ts
@@ -8,7 +8,7 @@
import * as ast from '../ast';
import * as literal from '../node_types/literal';
-import { IIndexPattern, KueryNode } from '../../..';
+import { IndexPatternBase, KueryNode } from '../../..';
export function buildNodeParams(path: any, child: any) {
const pathNode =
@@ -20,7 +20,7 @@ export function buildNodeParams(path: any, child: any) {
export function toElasticsearchQuery(
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config: Record = {},
context: Record = {}
) {
diff --git a/src/plugins/data/common/es_query/kuery/functions/not.ts b/src/plugins/data/common/es_query/kuery/functions/not.ts
index ef4456897bcdd..f837cd261c814 100644
--- a/src/plugins/data/common/es_query/kuery/functions/not.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/not.ts
@@ -7,7 +7,7 @@
*/
import * as ast from '../ast';
-import { IIndexPattern, KueryNode } from '../../..';
+import { IndexPatternBase, KueryNode } from '../../..';
export function buildNodeParams(child: KueryNode) {
return {
@@ -17,7 +17,7 @@ export function buildNodeParams(child: KueryNode) {
export function toElasticsearchQuery(
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config: Record = {},
context: Record = {}
) {
diff --git a/src/plugins/data/common/es_query/kuery/functions/or.ts b/src/plugins/data/common/es_query/kuery/functions/or.ts
index 416687e7cde9c..7365cc39595e6 100644
--- a/src/plugins/data/common/es_query/kuery/functions/or.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/or.ts
@@ -7,7 +7,7 @@
*/
import * as ast from '../ast';
-import { IIndexPattern, KueryNode } from '../../..';
+import { IndexPatternBase, KueryNode } from '../../..';
export function buildNodeParams(children: KueryNode[]) {
return {
@@ -17,7 +17,7 @@ export function buildNodeParams(children: KueryNode[]) {
export function toElasticsearchQuery(
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config: Record = {},
context: Record = {}
) {
diff --git a/src/plugins/data/common/es_query/kuery/functions/range.ts b/src/plugins/data/common/es_query/kuery/functions/range.ts
index 06b345e5821c3..caefa7e5373ca 100644
--- a/src/plugins/data/common/es_query/kuery/functions/range.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/range.ts
@@ -13,7 +13,7 @@ import { getRangeScript, RangeFilterParams } from '../../filters';
import { getFields } from './utils/get_fields';
import { getTimeZoneFromSettings } from '../../utils';
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
-import { IIndexPattern, KueryNode, IFieldType } from '../../..';
+import { IndexPatternBase, KueryNode, IFieldType } from '../../..';
export function buildNodeParams(fieldName: string, params: RangeFilterParams) {
const paramsToMap = _.pick(params, 'gt', 'lt', 'gte', 'lte', 'format');
@@ -33,7 +33,7 @@ export function buildNodeParams(fieldName: string, params: RangeFilterParams) {
export function toElasticsearchQuery(
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config: Record = {},
context: Record = {}
) {
diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts
index 4002a36648f04..7dac1262d5062 100644
--- a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts
@@ -8,10 +8,10 @@
import * as literal from '../../node_types/literal';
import * as wildcard from '../../node_types/wildcard';
-import { KueryNode, IIndexPattern } from '../../../..';
+import { KueryNode, IndexPatternBase } from '../../../..';
import { LiteralTypeBuildNode } from '../../node_types/types';
-export function getFields(node: KueryNode, indexPattern?: IIndexPattern) {
+export function getFields(node: KueryNode, indexPattern?: IndexPatternBase) {
if (!indexPattern) return [];
if (node.type === 'literal') {
const fieldName = literal.toElasticsearchQuery(node as LiteralTypeBuildNode);
diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts
index e623579226861..644791637aa70 100644
--- a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts
@@ -7,11 +7,11 @@
*/
import { getFields } from './get_fields';
-import { IIndexPattern, IFieldType, KueryNode } from '../../../..';
+import { IndexPatternBase, IFieldType, KueryNode } from '../../../..';
export function getFullFieldNameNode(
rootNameNode: any,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
nestedPath?: string
): KueryNode {
const fullFieldNameNode = {
diff --git a/src/plugins/data/common/es_query/kuery/node_types/function.ts b/src/plugins/data/common/es_query/kuery/node_types/function.ts
index b9b7379dfb23d..642089a101f31 100644
--- a/src/plugins/data/common/es_query/kuery/node_types/function.ts
+++ b/src/plugins/data/common/es_query/kuery/node_types/function.ts
@@ -9,7 +9,7 @@
import _ from 'lodash';
import { functions } from '../functions';
-import { IIndexPattern, KueryNode } from '../../..';
+import { IndexPatternBase, KueryNode } from '../../..';
import { FunctionName, FunctionTypeBuildNode } from './types';
export function buildNode(functionName: FunctionName, ...args: any[]) {
@@ -45,7 +45,7 @@ export function buildNodeWithArgumentNodes(
export function toElasticsearchQuery(
node: KueryNode,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config?: Record,
context?: Record
) {
diff --git a/src/plugins/data/common/es_query/kuery/node_types/types.ts b/src/plugins/data/common/es_query/kuery/node_types/types.ts
index b3247a0ad8dc2..ea8eb5e8a0618 100644
--- a/src/plugins/data/common/es_query/kuery/node_types/types.ts
+++ b/src/plugins/data/common/es_query/kuery/node_types/types.ts
@@ -11,8 +11,8 @@
*/
import { JsonValue } from '@kbn/common-utils';
-import { IIndexPattern } from '../../../index_patterns';
import { KueryNode } from '..';
+import { IndexPatternBase } from '../..';
export type FunctionName =
| 'is'
@@ -30,7 +30,7 @@ interface FunctionType {
buildNodeWithArgumentNodes: (functionName: FunctionName, args: any[]) => FunctionTypeBuildNode;
toElasticsearchQuery: (
node: any,
- indexPattern?: IIndexPattern,
+ indexPattern?: IndexPatternBase,
config?: Record,
context?: Record
) => JsonValue;
diff --git a/src/plugins/data/common/field_formats/converters/string.ts b/src/plugins/data/common/field_formats/converters/string.ts
index ec92d75910522..64367df5d90dd 100644
--- a/src/plugins/data/common/field_formats/converters/string.ts
+++ b/src/plugins/data/common/field_formats/converters/string.ts
@@ -13,6 +13,10 @@ import { FieldFormat } from '../field_format';
import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types';
import { shortenDottedString } from '../../utils';
+export const emptyLabel = i18n.translate('data.fieldFormats.string.emptyLabel', {
+ defaultMessage: '(empty)',
+});
+
const TRANSFORM_OPTIONS = [
{
kind: false,
@@ -103,6 +107,9 @@ export class StringFormat extends FieldFormat {
}
textConvert: TextContextTypeConvert = (val) => {
+ if (val === '') {
+ return emptyLabel;
+ }
switch (this.param('transform')) {
case 'lower':
return String(val).toLowerCase();
diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts
index 07aa8967b905e..a88f029c0c7cd 100644
--- a/src/plugins/data/common/index_patterns/types.ts
+++ b/src/plugins/data/common/index_patterns/types.ts
@@ -9,6 +9,7 @@ import type { estypes } from '@elastic/elasticsearch';
import { ToastInputFields, ErrorToastOptions } from 'src/core/public/notifications';
// eslint-disable-next-line
import type { SavedObject } from 'src/core/server';
+import type { IndexPatternBase } from '../es_query';
import { IFieldType } from './fields';
import { RUNTIME_FIELD_TYPES } from './constants';
import { SerializedFieldFormat } from '../../../expressions/common';
@@ -29,10 +30,8 @@ export interface RuntimeField {
* IIndexPattern allows for an IndexPattern OR an index pattern saved object
* Use IndexPattern or IndexPatternSpec instead
*/
-export interface IIndexPattern {
- fields: IFieldType[];
+export interface IIndexPattern extends IndexPatternBase {
title: string;
- id?: string;
/**
* Type is used for identifying rollup indices, otherwise left undefined
*/
diff --git a/src/plugins/data/common/search/expressions/phrase_filter.test.ts b/src/plugins/data/common/search/expressions/phrase_filter.test.ts
index 39bd907513a0d..a61cc0bfd68ab 100644
--- a/src/plugins/data/common/search/expressions/phrase_filter.test.ts
+++ b/src/plugins/data/common/search/expressions/phrase_filter.test.ts
@@ -32,7 +32,6 @@ describe('interpreter/functions#phraseFilter', () => {
"something",
],
"type": "phrases",
- "value": "test, something",
},
"query": Object {
"bool": Object {
diff --git a/src/plugins/data/config.ts b/src/plugins/data/config.ts
index 9306b64019bbc..1b7bfbc09ad16 100644
--- a/src/plugins/data/config.ts
+++ b/src/plugins/data/config.ts
@@ -44,10 +44,20 @@ export const searchSessionsConfigSchema = schema.object({
*/
pageSize: schema.number({ defaultValue: 100 }),
/**
- * trackingInterval controls how often we track search session objects progress
+ * trackingInterval controls how often we track persisted search session objects progress
*/
trackingInterval: schema.duration({ defaultValue: '10s' }),
+ /**
+ * cleanupInterval controls how often we track non-persisted search session objects for cleanup
+ */
+ cleanupInterval: schema.duration({ defaultValue: '60s' }),
+
+ /**
+ * expireInterval controls how often we track persisted search session objects for expiration
+ */
+ expireInterval: schema.duration({ defaultValue: '60m' }),
+
/**
* monitoringTaskTimeout controls for how long task manager waits for search session monitoring task to complete before considering it timed out,
* If tasks timeouts it receives cancel signal and next task starts in "trackingInterval" time
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index 078dd3a9b7c5a..d7667f20d517e 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -23,7 +23,6 @@ import {
disableFilter,
FILTERS,
FilterStateStore,
- getDisplayValueFromFilter,
getPhraseFilterField,
getPhraseFilterValue,
isExistsFilter,
@@ -43,6 +42,7 @@ import { FilterLabel } from './ui';
import { FilterItem } from './ui/filter_bar';
import {
+ getDisplayValueFromFilter,
generateFilters,
onlyDisabledFiltersChanged,
changeTimeFilter,
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 7a5f323e51459..2849b93b14483 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -808,11 +808,11 @@ export const esFilters: {
FILTERS: typeof FILTERS;
FilterStateStore: typeof FilterStateStore;
buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter;
- buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IIndexPattern) => import("../common").PhrasesFilter;
- buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IIndexPattern) => import("../common").ExistsFilter;
- buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IIndexPattern) => import("../common").PhraseFilter;
+ buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter;
+ buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter;
+ buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter;
buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter;
- buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IIndexPattern, formattedValue?: string | undefined) => import("../common").RangeFilter;
+ buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter;
isPhraseFilter: (filter: any) => filter is import("../common").PhraseFilter;
isExistsFilter: (filter: any) => filter is import("../common").ExistsFilter;
isPhrasesFilter: (filter: any) => filter is import("../common").PhrasesFilter;
@@ -858,7 +858,7 @@ export const esFilters: {
export const esKuery: {
nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes;
fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode;
- toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject;
+ toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IndexPatternBase | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject;
};
// Warning: (ae-missing-release-tag) "esQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -867,7 +867,7 @@ export const esKuery: {
export const esQuery: {
buildEsQuery: typeof buildEsQuery;
getEsQueryConfig: typeof getEsQueryConfig;
- buildQueryFromFilters: (filters: import("../common").Filter[] | undefined, indexPattern: import("../common").IIndexPattern | undefined, ignoreFilterIfFieldNotInIndex?: boolean) => {
+ buildQueryFromFilters: (filters: import("../common").Filter[] | undefined, indexPattern: import("../common").IndexPatternBase | undefined, ignoreFilterIfFieldNotInIndex?: boolean) => {
must: never[];
filter: import("../common").Filter[];
should: never[];
@@ -1286,22 +1286,19 @@ export interface IFieldType {
visualizable?: boolean;
}
+// Warning: (ae-forgotten-export) The symbol "IndexPatternBase" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "IIndexPattern" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public @deprecated (undocumented)
-export interface IIndexPattern {
+export interface IIndexPattern extends IndexPatternBase {
// Warning: (ae-forgotten-export) The symbol "SerializedFieldFormat" needs to be exported by the entry point index.d.ts
//
// (undocumented)
fieldFormatMap?: Record | undefined>;
- // (undocumented)
- fields: IFieldType[];
getFormatterForField?: (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat;
// (undocumented)
getTimeField?(): IFieldType | undefined;
// (undocumented)
- id?: string;
- // (undocumented)
timeFieldName?: string;
// (undocumented)
title: string;
@@ -2731,13 +2728,13 @@ export interface WaitUntilNextSessionCompletesOptions {
// Warnings were encountered during analysis:
//
-// src/plugins/data/common/es_query/filters/exists_filter.ts:19:3 - (ae-forgotten-export) The symbol "ExistsFilterMeta" needs to be exported by the entry point index.d.ts
-// src/plugins/data/common/es_query/filters/exists_filter.ts:20:3 - (ae-forgotten-export) The symbol "FilterExistsProperty" needs to be exported by the entry point index.d.ts
+// src/plugins/data/common/es_query/filters/exists_filter.ts:20:3 - (ae-forgotten-export) The symbol "ExistsFilterMeta" needs to be exported by the entry point index.d.ts
+// src/plugins/data/common/es_query/filters/exists_filter.ts:21:3 - (ae-forgotten-export) The symbol "FilterExistsProperty" needs to be exported by the entry point index.d.ts
// src/plugins/data/common/es_query/filters/match_all_filter.ts:17:3 - (ae-forgotten-export) The symbol "MatchAllFilterMeta" needs to be exported by the entry point index.d.ts
// src/plugins/data/common/es_query/filters/meta_filter.ts:43:3 - (ae-forgotten-export) The symbol "FilterState" needs to be exported by the entry point index.d.ts
// src/plugins/data/common/es_query/filters/meta_filter.ts:44:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts
-// src/plugins/data/common/es_query/filters/phrase_filter.ts:22:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts
-// src/plugins/data/common/es_query/filters/phrases_filter.ts:20:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts
+// src/plugins/data/common/es_query/filters/phrase_filter.ts:23:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts
+// src/plugins/data/common/es_query/filters/phrases_filter.ts:21:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts
// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:65:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts
// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:138:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts
// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:169:7 - (ae-forgotten-export) The symbol "RuntimeField" needs to be exported by the entry point index.d.ts
diff --git a/src/plugins/data/public/query/filter_manager/index.ts b/src/plugins/data/public/query/filter_manager/index.ts
index 327b9763541ac..55dba640b07b6 100644
--- a/src/plugins/data/public/query/filter_manager/index.ts
+++ b/src/plugins/data/public/query/filter_manager/index.ts
@@ -11,3 +11,5 @@ export { FilterManager } from './filter_manager';
export { mapAndFlattenFilters } from './lib/map_and_flatten_filters';
export { onlyDisabledFiltersChanged } from './lib/only_disabled';
export { generateFilters } from './lib/generate_filters';
+export { getDisplayValueFromFilter } from './lib/get_display_value';
+export { getIndexPatternFromFilter } from './lib/get_index_pattern_from_filter';
diff --git a/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts b/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts
new file mode 100644
index 0000000000000..48e1007534769
--- /dev/null
+++ b/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { stubIndexPattern, phraseFilter } from 'src/plugins/data/common/stubs';
+import { getDisplayValueFromFilter } from './get_display_value';
+
+describe('getDisplayValueFromFilter', () => {
+ it('returns the value if string', () => {
+ phraseFilter.meta.value = 'abc';
+ const displayValue = getDisplayValueFromFilter(phraseFilter, [stubIndexPattern]);
+ expect(displayValue).toBe('abc');
+ });
+
+ it('returns the value if undefined', () => {
+ phraseFilter.meta.value = undefined;
+ const displayValue = getDisplayValueFromFilter(phraseFilter, [stubIndexPattern]);
+ expect(displayValue).toBe('');
+ });
+
+ it('calls the value function if proivided', () => {
+ // The type of value currently doesn't match how it's used. Refactor needed.
+ phraseFilter.meta.value = jest.fn((x) => {
+ return 'abc';
+ }) as any;
+ const displayValue = getDisplayValueFromFilter(phraseFilter, [stubIndexPattern]);
+ expect(displayValue).toBe('abc');
+ expect(phraseFilter.meta.value).toHaveBeenCalledWith(undefined);
+ });
+
+ it('calls the value function if proivided, with formatter', () => {
+ stubIndexPattern.getFormatterForField = jest.fn().mockReturnValue('banana');
+ phraseFilter.meta.value = jest.fn((x) => {
+ return x + 'abc';
+ }) as any;
+ const displayValue = getDisplayValueFromFilter(phraseFilter, [stubIndexPattern]);
+ expect(stubIndexPattern.getFormatterForField).toHaveBeenCalledTimes(1);
+ expect(phraseFilter.meta.value).toHaveBeenCalledWith('banana');
+ expect(displayValue).toBe('bananaabc');
+ });
+});
diff --git a/src/plugins/data/common/es_query/filters/get_display_value.ts b/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts
similarity index 77%
rename from src/plugins/data/common/es_query/filters/get_display_value.ts
rename to src/plugins/data/public/query/filter_manager/lib/get_display_value.ts
index ee719843ae879..1ccfaacb24e4b 100644
--- a/src/plugins/data/common/es_query/filters/get_display_value.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts
@@ -7,9 +7,8 @@
*/
import { i18n } from '@kbn/i18n';
-import { IIndexPattern } from '../..';
+import { Filter, IIndexPattern } from '../../../../common';
import { getIndexPatternFromFilter } from './get_index_pattern_from_filter';
-import { Filter } from '../filters';
function getValueFormatter(indexPattern?: IIndexPattern, key?: string) {
// checking getFormatterForField exists because there is at least once case where an index pattern
@@ -29,11 +28,14 @@ function getValueFormatter(indexPattern?: IIndexPattern, key?: string) {
}
export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IIndexPattern[]): string {
- if (typeof filter.meta.value === 'function') {
+ const { key, value } = filter.meta;
+ if (typeof value === 'function') {
const indexPattern = getIndexPatternFromFilter(filter, indexPatterns);
- const valueFormatter: any = getValueFormatter(indexPattern, filter.meta.key);
- return (filter.meta.value as any)(valueFormatter);
+ const valueFormatter = getValueFormatter(indexPattern, key);
+ // TODO: distinguish between FilterMeta which is serializable to mapped FilterMeta
+ // Where value can be a function.
+ return (value as any)(valueFormatter);
} else {
- return filter.meta.value || '';
+ return value || '';
}
}
diff --git a/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/get_index_pattern_from_filter.test.ts
similarity index 100%
rename from src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/get_index_pattern_from_filter.test.ts
diff --git a/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.ts b/src/plugins/data/public/query/filter_manager/lib/get_index_pattern_from_filter.ts
similarity index 88%
rename from src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.ts
rename to src/plugins/data/public/query/filter_manager/lib/get_index_pattern_from_filter.ts
index bceeb5f2793ec..7a2ce29102e51 100644
--- a/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/get_index_pattern_from_filter.ts
@@ -6,8 +6,7 @@
* Side Public License, v 1.
*/
-import { Filter } from '../filters';
-import { IIndexPattern } from '../..';
+import { Filter, IIndexPattern } from '../../../../common';
export function getIndexPatternFromFilter(
filter: Filter,
diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts
index bfd528264b00f..5601dd66e5206 100644
--- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts
@@ -6,14 +6,29 @@
* Side Public License, v 1.
*/
-import { Filter, isPhrasesFilter } from '../../../../../common';
+import { Filter, FilterValueFormatter, isPhrasesFilter } from '../../../../../common';
+
+const getFormattedValueFn = (params: any) => {
+ return (formatter?: FilterValueFormatter) => {
+ return params
+ .map((v: any) => {
+ return formatter ? formatter.convert(v) : v;
+ })
+ .join(', ');
+ };
+};
export const mapPhrases = (filter: Filter) => {
if (!isPhrasesFilter(filter)) {
throw filter;
}
- const { type, key, value, params } = filter.meta;
+ const { type, key, params } = filter.meta;
- return { type, key, value, params };
+ return {
+ type,
+ key,
+ value: getFormattedValueFn(params),
+ params,
+ };
};
diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts
index 39680c4948366..7f388a29cd454 100644
--- a/src/plugins/data/public/search/session/session_service.test.ts
+++ b/src/plugins/data/public/search/session/session_service.test.ts
@@ -98,6 +98,14 @@ describe('Session service', () => {
expect(nowProvider.reset).toHaveBeenCalled();
});
+ it("Can clear other apps' session", async () => {
+ sessionService.start();
+ expect(sessionService.getSessionId()).not.toBeUndefined();
+ currentAppId$.next('change');
+ sessionService.clear();
+ expect(sessionService.getSessionId()).toBeUndefined();
+ });
+
it("Can start a new session in case there is other apps' stale session", async () => {
const s1 = sessionService.start();
expect(sessionService.getSessionId()).not.toBeUndefined();
diff --git a/src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx b/src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx
index 23de8327ce1f1..9cc9af04409f1 100644
--- a/src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx
+++ b/src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx
@@ -20,9 +20,9 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component } from 'react';
import { IIndexPattern } from '../..';
-import { getDisplayValueFromFilter, Filter } from '../../../common';
+import { Filter } from '../../../common';
import { FilterLabel } from '../filter_bar';
-import { mapAndFlattenFilters } from '../../query';
+import { mapAndFlattenFilters, getDisplayValueFromFilter } from '../../query';
interface Props {
filters: Filter[];
diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx
index 2b8978a125bca..734161ea87232 100644
--- a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx
+++ b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx
@@ -37,10 +37,10 @@ import { Operator } from './lib/filter_operators';
import { PhraseValueInput } from './phrase_value_input';
import { PhrasesValuesInput } from './phrases_values_input';
import { RangeValueInput } from './range_value_input';
+import { getIndexPatternFromFilter } from '../../../query';
import { IIndexPattern, IFieldType } from '../../..';
import {
Filter,
- getIndexPatternFromFilter,
FieldFilter,
buildFilter,
buildCustomFilter,
diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx
index 9e5090f945182..09e0571c2a870 100644
--- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx
+++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx
@@ -14,14 +14,13 @@ import { IUiSettingsClient } from 'src/core/public';
import { FilterEditor } from './filter_editor';
import { FilterView } from './filter_view';
import { IIndexPattern } from '../..';
+import { getDisplayValueFromFilter, getIndexPatternFromFilter } from '../../query';
import {
Filter,
isFilterPinned,
- getDisplayValueFromFilter,
toggleFilterNegated,
toggleFilterPinned,
toggleFilterDisabled,
- getIndexPatternFromFilter,
} from '../../../common';
import { getIndexPatterns } from '../../services';
diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
index a0a7e54d27532..0ab3f8a4e3466 100644
--- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
+++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
@@ -176,27 +176,27 @@ exports[`Inspector Data View component should render empty state 1`] = `
-
+
+
+
+
+
+
+
+
+
+
`;
exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when there are index patterns 1`] = `
-
-
+ />,
+ "rightSideItems": Array [],
}
- />
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
`;
exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when there are no index patterns 1`] = `
-
-
+ />,
+ "rightSideItems": Array [],
}
- />
-
-
+
+
+
+
+
+
+
+
+
+
`;
exports[`home welcome should show the normal home page if loading fails 1`] = `
-
-
+ />,
+ "rightSideItems": Array [],
}
- />
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
`;
exports[`home welcome should show the normal home page if welcome screen is disabled locally 1`] = `
-
-
+ />,
+ "rightSideItems": Array [],
}
- />
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
`;
exports[`home welcome should show the welcome screen if enabled, and there are no index patterns defined 1`] = `
@@ -824,53 +712,45 @@ exports[`home welcome should show the welcome screen if enabled, and there are n
`;
exports[`home welcome stores skip welcome setting if skipped 1`] = `
-
-
+ />,
+ "rightSideItems": Array [],
}
- />
-