diff --git a/.ci/Dockerfile b/.ci/Dockerfile
index d90d9f4710b5b..065b7f2aafd61 100644
--- a/.ci/Dockerfile
+++ b/.ci/Dockerfile
@@ -1,7 +1,7 @@
# NOTE: This Dockerfile is ONLY used to run certain tasks in CI. It is not used to run Kibana or as a distributable.
# If you're looking for the Kibana Docker image distributable, please see: src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts
-ARG NODE_VERSION=10.21.0
+ARG NODE_VERSION=10.22.0
FROM node:${NODE_VERSION} AS base
diff --git a/.ci/Jenkinsfile_security_cypress b/.ci/Jenkinsfile_security_cypress
new file mode 100644
index 0000000000000..d7f702a56563f
--- /dev/null
+++ b/.ci/Jenkinsfile_security_cypress
@@ -0,0 +1,28 @@
+#!/bin/groovy
+
+library 'kibana-pipeline-library'
+kibanaLibrary.load()
+
+kibanaPipeline(timeoutMinutes: 180) {
+ slackNotifications.onFailure(
+ disabled: !params.NOTIFY_ON_FAILURE,
+ channel: '#security-solution-slack-testing'
+ ) {
+ catchError {
+ withEnv([
+ 'CI_PARALLEL_PROCESS_NUMBER=1'
+ ]) {
+ def job = 'xpack-securityCypress'
+
+ workers.ci(name: job, size: 'l', ramDisk: true) {
+ kibanaPipeline.bash('test/scripts/jenkins_xpack_build_kibana.sh', 'Build Default Distributable')
+ kibanaPipeline.functionalTestProcess(job, 'test/scripts/jenkins_security_solution_cypress.sh')()
+ }
+ }
+ }
+ }
+
+ if (params.NOTIFY_ON_FAILURE) {
+ kibanaPipeline.sendMail(to: 'siem_dev_team@elastic.co')
+ }
+}
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 6863b91858ff6..7e34c931c5feb 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -8,7 +8,7 @@
/x-pack/plugins/lens/ @elastic/kibana-app
/x-pack/plugins/graph/ @elastic/kibana-app
/src/plugins/dashboard/ @elastic/kibana-app
-/src/plugins/dashboard/**/*.scss @elastic/kibana-core-ui
+/src/plugins/dashboard/**/*.scss @elastic/kibana-core-ui-designers
/src/plugins/discover/ @elastic/kibana-app
/src/plugins/input_control_vis/ @elastic/kibana-app
/src/plugins/kibana_legacy/ @elastic/kibana-app
@@ -70,7 +70,7 @@
# Canvas
/x-pack/plugins/canvas/ @elastic/kibana-canvas
-/x-pack/plugins/canvas/**/*.scss @elastic/kibana-core-ui
+/x-pack/plugins/canvas/**/*.scss @elastic/kibana-core-ui-designers
/x-pack/test/functional/apps/canvas/ @elastic/kibana-canvas
# Core UI
@@ -80,7 +80,7 @@
/src/plugins/home/server/services/ @elastic/kibana-core-ui
# Exclude tutorial resources folder for now because they are not owned by Kibana app and most will move out soon
/src/legacy/core_plugins/kibana/public/home/*.ts @elastic/kibana-core-ui
-/src/legacy/core_plugins/kibana/public/home/**/*.scss @elastic/kibana-core-ui
+/src/legacy/core_plugins/kibana/public/home/**/*.scss @elastic/kibana-core-ui-designers
/src/legacy/core_plugins/kibana/public/home/np_ready/ @elastic/kibana-core-ui
# Observability UIs
@@ -165,15 +165,26 @@
# Security
/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-platform
/x-pack/legacy/plugins/security/ @elastic/kibana-security
-/x-pack/legacy/plugins/security/**/*.scss @elastic/kibana-core-ui
+/x-pack/legacy/plugins/security/**/*.scss @elastic/kibana-core-ui-designers
/x-pack/legacy/plugins/spaces/ @elastic/kibana-security
-/x-pack/legacy/plugins/spaces/**/*.scss @elastic/kibana-core-ui
+/x-pack/legacy/plugins/spaces/**/*.scss @elastic/kibana-core-ui-designers
/x-pack/plugins/spaces/ @elastic/kibana-security
-/x-pack/plugins/spaces/**/*.scss @elastic/kibana-core-ui
+/x-pack/plugins/spaces/**/*.scss @elastic/kibana-core-ui-designers
/x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security
/x-pack/plugins/security/ @elastic/kibana-security
-/x-pack/plugins/security/**/*.scss @elastic/kibana-core-ui
+/x-pack/plugins/security/**/*.scss @elastic/kibana-core-ui-designers
/x-pack/test/api_integration/apis/security/ @elastic/kibana-security
+/x-pack/test/encrypted_saved_objects_api_integration/ @elastic/kibana-security
+/x-pack/test/functional/apps/security/ @elastic/kibana-security
+/x-pack/test/kerberos_api_integration/ @elastic/kibana-security
+/x-pack/test/login_selector_api_integration/ @elastic/kibana-security
+/x-pack/test/oidc_api_integration/ @elastic/kibana-security
+/x-pack/test/pki_api_integration/ @elastic/kibana-security
+/x-pack/test/saml_api_integration/ @elastic/kibana-security
+/x-pack/test/security_api_integration/ @elastic/kibana-security
+/x-pack/test/security_functional/ @elastic/kibana-security
+/x-pack/test/spaces_api_integration/ @elastic/kibana-security
+/x-pack/test/token_api_integration/ @elastic/kibana-security
# Kibana Localization
/src/dev/i18n/ @elastic/kibana-localization
diff --git a/.node-version b/.node-version
index b61c07ffddbd1..b7604b0c8aea6 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-10.21.0
+10.22.0
diff --git a/.nvmrc b/.nvmrc
index b61c07ffddbd1..b7604b0c8aea6 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-10.21.0
+10.22.0
diff --git a/docs/developer/architecture/code-exploration.asciidoc b/docs/developer/architecture/code-exploration.asciidoc
index d9502e4cb47ee..6e814921d3f32 100644
--- a/docs/developer/architecture/code-exploration.asciidoc
+++ b/docs/developer/architecture/code-exploration.asciidoc
@@ -313,9 +313,10 @@ To access an elasticsearch instance that has live data you have two options:
WARNING: Missing README.
-- {kib-repo}blob/{branch}/x-pack/plugins/beats_management[beats_management]
+- {kib-repo}blob/{branch}/x-pack/plugins/beats_management/readme.md[beatsManagement]
-WARNING: Missing README.
+Notes:
+Failure to have auth enabled in Kibana will make for a broken UI. UI-based errors not yet in place
- {kib-repo}blob/{branch}/x-pack/plugins/canvas/README.md[canvas]
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
index 842f90b7047c8..85e1da08b00af 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
@@ -96,5 +96,6 @@ readonly links: {
readonly dateMath: string;
};
readonly management: Record;
+ readonly visualize: Record;
};
```
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
index 8f739950d249b..fa2d9090e3159 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
@@ -17,5 +17,5 @@ export interface DocLinksStart
| --- | --- | --- |
| [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string
| |
| [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string
| |
-| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
}
| |
+| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
}
| |
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md
index c683f0ba33189..abcbbf18a8f9c 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md
@@ -13,6 +13,7 @@
```typescript
legacy: {
+ readonly config$: Observable;
readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
};
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md
index 0dd41a6154a1e..ca6134cd5ed65 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md
@@ -15,5 +15,5 @@ export interface ElasticsearchServiceSetup
| Property | Type | Description |
| --- | --- | --- |
-| [legacy](./kibana-plugin-core-server.elasticsearchservicesetup.legacy.md) | {
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
}
| |
+| [legacy](./kibana-plugin-core-server.elasticsearchservicesetup.legacy.md) | {
readonly config$: Observable<ElasticsearchConfig>;
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
}
| |
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md
index 5f346d7887c2a..4026483894aa1 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md
@@ -13,6 +13,7 @@
```typescript
legacy: {
+ readonly config$: Observable;
readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
};
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md
index 860867d654435..8d9cd1be148cf 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md
@@ -17,5 +17,5 @@ export interface ElasticsearchServiceStart
| --- | --- | --- |
| [client](./kibana-plugin-core-server.elasticsearchservicestart.client.md) | IClusterClient
| A pre-configured [Elasticsearch client](./kibana-plugin-core-server.iclusterclient.md) |
| [createClient](./kibana-plugin-core-server.elasticsearchservicestart.createclient.md) | (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient
| Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). |
-| [legacy](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) | {
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
}
| |
+| [legacy](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) | {
readonly config$: Observable<ElasticsearchConfig>;
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
}
| |
diff --git a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md
index fee6124f8d866..17fafe2af0de1 100644
--- a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md
+++ b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.md
@@ -19,6 +19,6 @@ export interface RouteConfigOptions
| [authRequired](./kibana-plugin-core-server.routeconfigoptions.authrequired.md) | boolean | 'optional'
| Defines authentication mode for a route: - true. A user has to have valid credentials to access a resource - false. A user can access a resource without any credentials. - 'optional'. A user can access a resource if has valid credentials or no credentials at all. Can be useful when we grant access to a resource but want to identify a user if possible.Defaults to true
if an auth mechanism is registered. |
| [body](./kibana-plugin-core-server.routeconfigoptions.body.md) | Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody
| Additional body options [RouteConfigOptionsBody](./kibana-plugin-core-server.routeconfigoptionsbody.md). |
| [tags](./kibana-plugin-core-server.routeconfigoptions.tags.md) | readonly string[]
| Additional metadata tag strings to attach to the route. |
-| [timeout](./kibana-plugin-core-server.routeconfigoptions.timeout.md) | number
| Timeouts for processing durations. Response timeout is in milliseconds. Default value: 2 minutes |
+| [timeout](./kibana-plugin-core-server.routeconfigoptions.timeout.md) | {
payload?: Method extends 'get' | 'options' ? undefined : number;
idleSocket?: number;
}
| Defines per-route timeouts. |
| [xsrfRequired](./kibana-plugin-core-server.routeconfigoptions.xsrfrequired.md) | Method extends 'get' ? never : boolean
| Defines xsrf protection requirements for a route: - true. Requires an incoming POST/PUT/DELETE request to contain kbn-xsrf
header. - false. Disables xsrf protection.Set to true by default |
diff --git a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md
index 479fcf883ec4d..f602a8913964f 100644
--- a/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md
+++ b/docs/development/core/server/kibana-plugin-core-server.routeconfigoptions.timeout.md
@@ -4,10 +4,13 @@
## RouteConfigOptions.timeout property
-Timeouts for processing durations. Response timeout is in milliseconds. Default value: 2 minutes
+Defines per-route timeouts.
Signature:
```typescript
-timeout?: number;
+timeout?: {
+ payload?: Method extends 'get' | 'options' ? undefined : number;
+ idleSocket?: number;
+ };
```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.baseformatterspublic.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.baseformatterspublic.md
index 25f046983cbce..1aa9f460c4fac 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.baseformatterspublic.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.baseformatterspublic.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-baseFormattersPublic: (import("../../common").FieldFormatInstanceType | typeof DateNanosFormat | typeof DateFormat)[]
+baseFormattersPublic: (import("../../common").FieldFormatInstanceType | typeof DateFormat | typeof DateNanosFormat)[]
```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md
new file mode 100644
index 0000000000000..0bd00e937eaaa
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [EsdslExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md)
+
+## EsdslExpressionFunctionDefinition type
+
+Signature:
+
+```typescript
+export declare type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md
new file mode 100644
index 0000000000000..b95ae3c69bf20
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [EsRawResponseExpressionTypeDefinition](./kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md)
+
+## EsRawResponseExpressionTypeDefinition type
+
+Signature:
+
+```typescript
+export declare type EsRawResponseExpressionTypeDefinition = ExpressionTypeDefinition;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
index 53c30b52cb985..dc83cfb930d7d 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
@@ -126,7 +126,9 @@
| [AggParam](./kibana-plugin-plugins-data-public.aggparam.md) | |
| [CustomFilter](./kibana-plugin-plugins-data-public.customfilter.md) | |
| [EsaggsExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esaggsexpressionfunctiondefinition.md) | |
+| [EsdslExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md) | |
| [EsQuerySortValue](./kibana-plugin-plugins-data-public.esquerysortvalue.md) | |
+| [EsRawResponseExpressionTypeDefinition](./kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md) | |
| [ExistsFilter](./kibana-plugin-plugins-data-public.existsfilter.md) | |
| [FieldFormatId](./kibana-plugin-plugins-data-public.fieldformatid.md) | id type is needed for creating custom converters. |
| [FieldFormatsContentType](./kibana-plugin-plugins-data-public.fieldformatscontenttype.md) | \* |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md
index 6c8f7fbdb170b..22dc92c275670 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md
@@ -14,7 +14,7 @@ search: {
intervalOptions: ({
display: string;
val: string;
- enabled(agg: import("./search/aggs/buckets/bucket_agg_type").IBucketAggConfig): boolean | "" | undefined;
+ enabled(agg: import("../common").IBucketAggConfig): boolean | "" | undefined;
} | {
display: string;
val: string;
@@ -23,9 +23,9 @@ search: {
InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError;
Ipv4Address: typeof Ipv4Address;
isDateHistogramBucketAggConfig: typeof isDateHistogramBucketAggConfig;
- isNumberType: (agg: import("./search").AggConfig) => boolean;
- isStringType: (agg: import("./search").AggConfig) => boolean;
- isType: (...types: string[]) => (agg: import("./search").AggConfig) => boolean;
+ isNumberType: (agg: import("../common").AggConfig) => boolean;
+ isStringType: (agg: import("../common").AggConfig) => boolean;
+ isType: (...types: string[]) => (agg: import("../common").AggConfig) => boolean;
isValidEsInterval: typeof isValidEsInterval;
isValidInterval: typeof isValidInterval;
parentPipelineType: string;
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggconfigoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggconfigoptions.md
new file mode 100644
index 0000000000000..effb2e798ad6f
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggconfigoptions.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggConfigOptions](./kibana-plugin-plugins-data-server.aggconfigoptions.md)
+
+## AggConfigOptions type
+
+Signature:
+
+```typescript
+export declare type AggConfigOptions = Assign;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.agggrouplabels.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.agggrouplabels.md
new file mode 100644
index 0000000000000..cf0caee6ac33e
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.agggrouplabels.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggGroupLabels](./kibana-plugin-plugins-data-server.agggrouplabels.md)
+
+## AggGroupLabels variable
+
+Signature:
+
+```typescript
+AggGroupLabels: {
+ buckets: string;
+ metrics: string;
+ none: string;
+}
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.agggroupname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.agggroupname.md
new file mode 100644
index 0000000000000..403294eba1367
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.agggroupname.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggGroupName](./kibana-plugin-plugins-data-server.agggroupname.md)
+
+## AggGroupName type
+
+Signature:
+
+```typescript
+export declare type AggGroupName = $Values;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.agggroupnames.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.agggroupnames.md
new file mode 100644
index 0000000000000..11d194723c521
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.agggroupnames.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggGroupNames](./kibana-plugin-plugins-data-server.agggroupnames.md)
+
+## AggGroupNames variable
+
+Signature:
+
+```typescript
+AggGroupNames: Readonly<{
+ Buckets: "buckets";
+ Metrics: "metrics";
+ None: "none";
+}>
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparam.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparam.md
new file mode 100644
index 0000000000000..893501666b9a0
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparam.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggParam](./kibana-plugin-plugins-data-server.aggparam.md)
+
+## AggParam type
+
+Signature:
+
+```typescript
+export declare type AggParam = BaseParamType;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.display.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.display.md
new file mode 100644
index 0000000000000..1030056e16afe
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.display.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) > [display](./kibana-plugin-plugins-data-server.aggparamoption.display.md)
+
+## AggParamOption.display property
+
+Signature:
+
+```typescript
+display: string;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.enabled.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.enabled.md
new file mode 100644
index 0000000000000..8b1fcc4a1bbd0
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.enabled.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) > [enabled](./kibana-plugin-plugins-data-server.aggparamoption.enabled.md)
+
+## AggParamOption.enabled() method
+
+Signature:
+
+```typescript
+enabled?(agg: AggConfig): boolean;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| agg | AggConfig
| |
+
+Returns:
+
+`boolean`
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.md
new file mode 100644
index 0000000000000..a7ddcf395cab4
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.md
@@ -0,0 +1,25 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md)
+
+## AggParamOption interface
+
+Signature:
+
+```typescript
+export interface AggParamOption
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [display](./kibana-plugin-plugins-data-server.aggparamoption.display.md) | string
| |
+| [val](./kibana-plugin-plugins-data-server.aggparamoption.val.md) | string
| |
+
+## Methods
+
+| Method | Description |
+| --- | --- |
+| [enabled(agg)](./kibana-plugin-plugins-data-server.aggparamoption.enabled.md) | |
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.val.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.val.md
new file mode 100644
index 0000000000000..2c87c91c294d9
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamoption.val.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) > [val](./kibana-plugin-plugins-data-server.aggparamoption.val.md)
+
+## AggParamOption.val property
+
+Signature:
+
+```typescript
+val: string;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype._constructor_.md
new file mode 100644
index 0000000000000..2e1b16855987e
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype._constructor_.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggParamType](./kibana-plugin-plugins-data-server.aggparamtype.md) > [(constructor)](./kibana-plugin-plugins-data-server.aggparamtype._constructor_.md)
+
+## AggParamType.(constructor)
+
+Constructs a new instance of the `AggParamType` class
+
+Signature:
+
+```typescript
+constructor(config: Record);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| config | Record<string, any>
| |
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype.allowedaggs.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype.allowedaggs.md
new file mode 100644
index 0000000000000..36179a9ce3569
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype.allowedaggs.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggParamType](./kibana-plugin-plugins-data-server.aggparamtype.md) > [allowedAggs](./kibana-plugin-plugins-data-server.aggparamtype.allowedaggs.md)
+
+## AggParamType.allowedAggs property
+
+Signature:
+
+```typescript
+allowedAggs: string[];
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype.makeagg.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype.makeagg.md
new file mode 100644
index 0000000000000..bd5d2fca77659
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype.makeagg.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggParamType](./kibana-plugin-plugins-data-server.aggparamtype.md) > [makeAgg](./kibana-plugin-plugins-data-server.aggparamtype.makeagg.md)
+
+## AggParamType.makeAgg property
+
+Signature:
+
+```typescript
+makeAgg: (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype.md
new file mode 100644
index 0000000000000..00c1906dd880b
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.aggparamtype.md
@@ -0,0 +1,25 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggParamType](./kibana-plugin-plugins-data-server.aggparamtype.md)
+
+## AggParamType class
+
+Signature:
+
+```typescript
+export declare class AggParamType extends BaseParamType
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(config)](./kibana-plugin-plugins-data-server.aggparamtype._constructor_.md) | | Constructs a new instance of the AggParamType
class |
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [allowedAggs](./kibana-plugin-plugins-data-server.aggparamtype.allowedaggs.md) | | string[]
| |
+| [makeAgg](./kibana-plugin-plugins-data-server.aggparamtype.makeagg.md) | | (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig
| |
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.bucket_types.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.bucket_types.md
new file mode 100644
index 0000000000000..568e435754545
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.bucket_types.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [BUCKET\_TYPES](./kibana-plugin-plugins-data-server.bucket_types.md)
+
+## BUCKET\_TYPES enum
+
+Signature:
+
+```typescript
+export declare enum BUCKET_TYPES
+```
+
+## Enumeration Members
+
+| Member | Value | Description |
+| --- | --- | --- |
+| DATE\_HISTOGRAM | "date_histogram"
| |
+| DATE\_RANGE | "date_range"
| |
+| FILTER | "filter"
| |
+| FILTERS | "filters"
| |
+| GEOHASH\_GRID | "geohash_grid"
| |
+| GEOTILE\_GRID | "geotile_grid"
| |
+| HISTOGRAM | "histogram"
| |
+| IP\_RANGE | "ip_range"
| |
+| RANGE | "range"
| |
+| SIGNIFICANT\_TERMS | "significant_terms"
| |
+| TERMS | "terms"
| |
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iaggconfig.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iaggconfig.md
new file mode 100644
index 0000000000000..261b6e0b3bac1
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iaggconfig.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IAggConfig](./kibana-plugin-plugins-data-server.iaggconfig.md)
+
+## IAggConfig type
+
+ AggConfig
+
+ This class represents an aggregation, which is displayed in the left-hand nav of the Visualize app.
+
+Signature:
+
+```typescript
+export declare type IAggConfig = AggConfig;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iaggtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iaggtype.md
new file mode 100644
index 0000000000000..d5868e1b0917e
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iaggtype.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IAggType](./kibana-plugin-plugins-data-server.iaggtype.md)
+
+## IAggType type
+
+Signature:
+
+```typescript
+export declare type IAggType = AggType;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldparamtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldparamtype.md
new file mode 100644
index 0000000000000..4937245647f4e
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldparamtype.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldParamType](./kibana-plugin-plugins-data-server.ifieldparamtype.md)
+
+## IFieldParamType type
+
+Signature:
+
+```typescript
+export declare type IFieldParamType = FieldParamType;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.imetricaggtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.imetricaggtype.md
new file mode 100644
index 0000000000000..ae779c2b1510f
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.imetricaggtype.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IMetricAggType](./kibana-plugin-plugins-data-server.imetricaggtype.md)
+
+## IMetricAggType type
+
+Signature:
+
+```typescript
+export declare type IMetricAggType = MetricAggType;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.aggs.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.aggs.md
new file mode 100644
index 0000000000000..86bd4ab694e11
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.aggs.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) > [aggs](./kibana-plugin-plugins-data-server.isearchsetup.aggs.md)
+
+## ISearchSetup.aggs property
+
+Signature:
+
+```typescript
+aggs: AggsSetup;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md
index d9749bc44f45a..e5b11a0b997ea 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md
@@ -14,6 +14,7 @@ export interface ISearchSetup
| Property | Type | Description |
| --- | --- | --- |
+| [aggs](./kibana-plugin-plugins-data-server.isearchsetup.aggs.md) | AggsSetup
| |
| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | (name: string, strategy: ISearchStrategy) => void
| Extension point exposed for other plugins to register their own search strategies. |
| [usage](./kibana-plugin-plugins-data-server.isearchsetup.usage.md) | SearchUsage
| Used internally for telemetry |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.aggs.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.aggs.md
new file mode 100644
index 0000000000000..8da429a07708c
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.aggs.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) > [aggs](./kibana-plugin-plugins-data-server.isearchstart.aggs.md)
+
+## ISearchStart.aggs property
+
+Signature:
+
+```typescript
+aggs: AggsStart;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md
index 308ce3cb568bc..3762da963d4d9 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md
@@ -14,6 +14,7 @@ export interface ISearchStart
| Property | Type | Description |
| --- | --- | --- |
+| [aggs](./kibana-plugin-plugins-data-server.isearchstart.aggs.md) | AggsStart
| |
| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | (name: string) => ISearchStrategy
| Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. |
| [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) | (context: RequestHandlerContext, request: IKibanaSearchRequest, options: ISearchOptions) => Promise<IKibanaSearchResponse>
| |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md
index f472064c87755..0292e08063fbb 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md
@@ -8,15 +8,19 @@
| Class | Description |
| --- | --- |
+| [AggParamType](./kibana-plugin-plugins-data-server.aggparamtype.md) | |
| [IndexPatternsFetcher](./kibana-plugin-plugins-data-server.indexpatternsfetcher.md) | |
+| [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md) | |
| [Plugin](./kibana-plugin-plugins-data-server.plugin.md) | |
## Enumerations
| Enumeration | Description |
| --- | --- |
+| [BUCKET\_TYPES](./kibana-plugin-plugins-data-server.bucket_types.md) | |
| [ES\_FIELD\_TYPES](./kibana-plugin-plugins-data-server.es_field_types.md) | \* |
| [KBN\_FIELD\_TYPES](./kibana-plugin-plugins-data-server.kbn_field_types.md) | \* |
+| [METRIC\_TYPES](./kibana-plugin-plugins-data-server.metric_types.md) | |
## Functions
@@ -33,6 +37,7 @@
| Interface | Description |
| --- | --- |
+| [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) | |
| [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | |
| [FieldFormatConfig](./kibana-plugin-plugins-data-server.fieldformatconfig.md) | |
| [Filter](./kibana-plugin-plugins-data-server.filter.md) | |
@@ -48,17 +53,22 @@
| [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | |
| [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) | Search strategy interface contains a search method that takes in a request and returns a promise that resolves to a response. |
| [KueryNode](./kibana-plugin-plugins-data-server.kuerynode.md) | |
+| [OptionedValueProp](./kibana-plugin-plugins-data-server.optionedvalueprop.md) | |
| [PluginSetup](./kibana-plugin-plugins-data-server.pluginsetup.md) | |
| [PluginStart](./kibana-plugin-plugins-data-server.pluginstart.md) | |
| [Query](./kibana-plugin-plugins-data-server.query.md) | |
| [RefreshInterval](./kibana-plugin-plugins-data-server.refreshinterval.md) | |
| [SearchUsage](./kibana-plugin-plugins-data-server.searchusage.md) | |
+| [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) | \* |
+| [TabbedTable](./kibana-plugin-plugins-data-server.tabbedtable.md) | \* |
| [TimeRange](./kibana-plugin-plugins-data-server.timerange.md) | |
## Variables
| Variable | Description |
| --- | --- |
+| [AggGroupLabels](./kibana-plugin-plugins-data-server.agggrouplabels.md) | |
+| [AggGroupNames](./kibana-plugin-plugins-data-server.agggroupnames.md) | |
| [castEsToKbnFieldTypeName](./kibana-plugin-plugins-data-server.castestokbnfieldtypename.md) | Get the KbnFieldType name for an esType string |
| [config](./kibana-plugin-plugins-data-server.config.md) | |
| [esFilters](./kibana-plugin-plugins-data-server.esfilters.md) | |
@@ -73,8 +83,16 @@
| Type Alias | Description |
| --- | --- |
+| [AggConfigOptions](./kibana-plugin-plugins-data-server.aggconfigoptions.md) | |
+| [AggGroupName](./kibana-plugin-plugins-data-server.agggroupname.md) | |
+| [AggParam](./kibana-plugin-plugins-data-server.aggparam.md) | |
| [EsaggsExpressionFunctionDefinition](./kibana-plugin-plugins-data-server.esaggsexpressionfunctiondefinition.md) | |
| [FieldFormatsGetConfigFn](./kibana-plugin-plugins-data-server.fieldformatsgetconfigfn.md) | |
+| [IAggConfig](./kibana-plugin-plugins-data-server.iaggconfig.md) | AggConfig This class represents an aggregation, which is displayed in the left-hand nav of the Visualize app. |
+| [IAggType](./kibana-plugin-plugins-data-server.iaggtype.md) | |
| [IFieldFormatsRegistry](./kibana-plugin-plugins-data-server.ifieldformatsregistry.md) | |
+| [IFieldParamType](./kibana-plugin-plugins-data-server.ifieldparamtype.md) | |
+| [IMetricAggType](./kibana-plugin-plugins-data-server.imetricaggtype.md) | |
| [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | |
+| [TabbedAggRow](./kibana-plugin-plugins-data-server.tabbedaggrow.md) | \* |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.metric_types.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.metric_types.md
new file mode 100644
index 0000000000000..49df98b6d70a1
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.metric_types.md
@@ -0,0 +1,38 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [METRIC\_TYPES](./kibana-plugin-plugins-data-server.metric_types.md)
+
+## METRIC\_TYPES enum
+
+Signature:
+
+```typescript
+export declare enum METRIC_TYPES
+```
+
+## Enumeration Members
+
+| Member | Value | Description |
+| --- | --- | --- |
+| AVG | "avg"
| |
+| AVG\_BUCKET | "avg_bucket"
| |
+| CARDINALITY | "cardinality"
| |
+| COUNT | "count"
| |
+| CUMULATIVE\_SUM | "cumulative_sum"
| |
+| DERIVATIVE | "derivative"
| |
+| GEO\_BOUNDS | "geo_bounds"
| |
+| GEO\_CENTROID | "geo_centroid"
| |
+| MAX | "max"
| |
+| MAX\_BUCKET | "max_bucket"
| |
+| MEDIAN | "median"
| |
+| MIN | "min"
| |
+| MIN\_BUCKET | "min_bucket"
| |
+| MOVING\_FN | "moving_avg"
| |
+| PERCENTILE\_RANKS | "percentile_ranks"
| |
+| PERCENTILES | "percentiles"
| |
+| SERIAL\_DIFF | "serial_diff"
| |
+| STD\_DEV | "std_dev"
| |
+| SUM | "sum"
| |
+| SUM\_BUCKET | "sum_bucket"
| |
+| TOP\_HITS | "top_hits"
| |
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedparamtype._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedparamtype._constructor_.md
new file mode 100644
index 0000000000000..3b2fd2218709a
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedparamtype._constructor_.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md) > [(constructor)](./kibana-plugin-plugins-data-server.optionedparamtype._constructor_.md)
+
+## OptionedParamType.(constructor)
+
+Constructs a new instance of the `OptionedParamType` class
+
+Signature:
+
+```typescript
+constructor(config: Record);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| config | Record<string, any>
| |
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedparamtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedparamtype.md
new file mode 100644
index 0000000000000..6bf2ef4baa915
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedparamtype.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md)
+
+## OptionedParamType class
+
+Signature:
+
+```typescript
+export declare class OptionedParamType extends BaseParamType
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(config)](./kibana-plugin-plugins-data-server.optionedparamtype._constructor_.md) | | Constructs a new instance of the OptionedParamType
class |
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [options](./kibana-plugin-plugins-data-server.optionedparamtype.options.md) | | OptionedValueProp[]
| |
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedparamtype.options.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedparamtype.options.md
new file mode 100644
index 0000000000000..868619ad5a9e0
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedparamtype.options.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md) > [options](./kibana-plugin-plugins-data-server.optionedparamtype.options.md)
+
+## OptionedParamType.options property
+
+Signature:
+
+```typescript
+options: OptionedValueProp[];
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.disabled.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.disabled.md
new file mode 100644
index 0000000000000..e0a21a8727614
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.disabled.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [OptionedValueProp](./kibana-plugin-plugins-data-server.optionedvalueprop.md) > [disabled](./kibana-plugin-plugins-data-server.optionedvalueprop.disabled.md)
+
+## OptionedValueProp.disabled property
+
+Signature:
+
+```typescript
+disabled?: boolean;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.iscompatible.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.iscompatible.md
new file mode 100644
index 0000000000000..de3ecc0b97a64
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.iscompatible.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [OptionedValueProp](./kibana-plugin-plugins-data-server.optionedvalueprop.md) > [isCompatible](./kibana-plugin-plugins-data-server.optionedvalueprop.iscompatible.md)
+
+## OptionedValueProp.isCompatible property
+
+Signature:
+
+```typescript
+isCompatible: (agg: IAggConfig) => boolean;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.md
new file mode 100644
index 0000000000000..ef2440035c83b
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [OptionedValueProp](./kibana-plugin-plugins-data-server.optionedvalueprop.md)
+
+## OptionedValueProp interface
+
+Signature:
+
+```typescript
+export interface OptionedValueProp
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [disabled](./kibana-plugin-plugins-data-server.optionedvalueprop.disabled.md) | boolean
| |
+| [isCompatible](./kibana-plugin-plugins-data-server.optionedvalueprop.iscompatible.md) | (agg: IAggConfig) => boolean
| |
+| [text](./kibana-plugin-plugins-data-server.optionedvalueprop.text.md) | string
| |
+| [value](./kibana-plugin-plugins-data-server.optionedvalueprop.value.md) | string
| |
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.text.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.text.md
new file mode 100644
index 0000000000000..0a2b3ac708038
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.text.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [OptionedValueProp](./kibana-plugin-plugins-data-server.optionedvalueprop.md) > [text](./kibana-plugin-plugins-data-server.optionedvalueprop.text.md)
+
+## OptionedValueProp.text property
+
+Signature:
+
+```typescript
+text: string;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.value.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.value.md
new file mode 100644
index 0000000000000..76618558d0479
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.optionedvalueprop.value.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [OptionedValueProp](./kibana-plugin-plugins-data-server.optionedvalueprop.md) > [value](./kibana-plugin-plugins-data-server.optionedvalueprop.value.md)
+
+## OptionedValueProp.value property
+
+Signature:
+
+```typescript
+value: string;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md
index a6fdfdf6891c8..18fca3d2c8a66 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md
@@ -7,10 +7,10 @@
Signature:
```typescript
-setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): {
+setup(core: CoreSetup, { expressions, usageCollection }: DataPluginSetupDependencies): {
search: ISearchSetup;
fieldFormats: {
- register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number;
+ register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number;
};
};
```
@@ -19,15 +19,15 @@ setup(core: CoreSetup, { usageCollection }: DataPluginS
| Parameter | Type | Description |
| --- | --- | --- |
-| core | CoreSetup<object, DataPluginStart>
| |
-| { usageCollection } | DataPluginSetupDependencies
| |
+| core | CoreSetup<DataPluginStartDependencies, DataPluginStart>
| |
+| { expressions, usageCollection } | DataPluginSetupDependencies
| |
Returns:
`{
search: ISearchSetup;
fieldFormats: {
- register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number;
+ register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number;
};
}`
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md
index 09563358100b3..9e38ce3f64f38 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md
@@ -9,15 +9,35 @@
```typescript
search: {
aggs: {
+ CidrMask: typeof CidrMask;
dateHistogramInterval: typeof dateHistogramInterval;
+ intervalOptions: ({
+ display: string;
+ val: string;
+ enabled(agg: import("../common").IBucketAggConfig): boolean | "" | undefined;
+ } | {
+ display: string;
+ val: string;
+ })[];
InvalidEsCalendarIntervalError: typeof InvalidEsCalendarIntervalError;
InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError;
Ipv4Address: typeof Ipv4Address;
+ isNumberType: (agg: import("../common").AggConfig) => boolean;
+ isStringType: (agg: import("../common").AggConfig) => boolean;
+ isType: (...types: string[]) => (agg: import("../common").AggConfig) => boolean;
isValidEsInterval: typeof isValidEsInterval;
isValidInterval: typeof isValidInterval;
+ parentPipelineType: string;
parseEsInterval: typeof parseEsInterval;
parseInterval: typeof parseInterval;
+ propFilter: typeof propFilter;
+ siblingPipelineType: string;
+ termsAggFilter: string[];
toAbsoluteDates: typeof toAbsoluteDates;
};
+ getRequestInspectorStats: typeof getRequestInspectorStats;
+ getResponseInspectorStats: typeof getResponseInspectorStats;
+ tabifyAggResponse: typeof tabifyAggResponse;
+ tabifyGetColumns: typeof tabifyGetColumns;
}
```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.aggconfig.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.aggconfig.md
new file mode 100644
index 0000000000000..9870f7380e16a
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.aggconfig.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) > [aggConfig](./kibana-plugin-plugins-data-server.tabbedaggcolumn.aggconfig.md)
+
+## TabbedAggColumn.aggConfig property
+
+Signature:
+
+```typescript
+aggConfig: IAggConfig;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.id.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.id.md
new file mode 100644
index 0000000000000..4f5a964a07a0c
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.id.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) > [id](./kibana-plugin-plugins-data-server.tabbedaggcolumn.id.md)
+
+## TabbedAggColumn.id property
+
+Signature:
+
+```typescript
+id: string;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.md
new file mode 100644
index 0000000000000..5e47f745fa17a
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md)
+
+## TabbedAggColumn interface
+
+\*
+
+Signature:
+
+```typescript
+export interface TabbedAggColumn
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [aggConfig](./kibana-plugin-plugins-data-server.tabbedaggcolumn.aggconfig.md) | IAggConfig
| |
+| [id](./kibana-plugin-plugins-data-server.tabbedaggcolumn.id.md) | string
| |
+| [name](./kibana-plugin-plugins-data-server.tabbedaggcolumn.name.md) | string
| |
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.name.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.name.md
new file mode 100644
index 0000000000000..8a07e2708066f
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.name.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) > [name](./kibana-plugin-plugins-data-server.tabbedaggcolumn.name.md)
+
+## TabbedAggColumn.name property
+
+Signature:
+
+```typescript
+name: string;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggrow.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggrow.md
new file mode 100644
index 0000000000000..d592aeff89639
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggrow.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedAggRow](./kibana-plugin-plugins-data-server.tabbedaggrow.md)
+
+## TabbedAggRow type
+
+\*
+
+Signature:
+
+```typescript
+export declare type TabbedAggRow = Record;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.columns.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.columns.md
new file mode 100644
index 0000000000000..55f079c581c8b
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.columns.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedTable](./kibana-plugin-plugins-data-server.tabbedtable.md) > [columns](./kibana-plugin-plugins-data-server.tabbedtable.columns.md)
+
+## TabbedTable.columns property
+
+Signature:
+
+```typescript
+columns: TabbedAggColumn[];
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.md
new file mode 100644
index 0000000000000..1bb055a2a3ce9
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedTable](./kibana-plugin-plugins-data-server.tabbedtable.md)
+
+## TabbedTable interface
+
+\*
+
+Signature:
+
+```typescript
+export interface TabbedTable
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [columns](./kibana-plugin-plugins-data-server.tabbedtable.columns.md) | TabbedAggColumn[]
| |
+| [rows](./kibana-plugin-plugins-data-server.tabbedtable.rows.md) | TabbedAggRow[]
| |
+
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.rows.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.rows.md
new file mode 100644
index 0000000000000..b783919a26573
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.rows.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedTable](./kibana-plugin-plugins-data-server.tabbedtable.md) > [rows](./kibana-plugin-plugins-data-server.tabbedtable.rows.md)
+
+## TabbedTable.rows property
+
+Signature:
+
+```typescript
+rows: TabbedAggRow[];
+```
diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc
index 7dc360fd721f4..9f13c152b4cbe 100644
--- a/docs/management/advanced-options.asciidoc
+++ b/docs/management/advanced-options.asciidoc
@@ -222,19 +222,19 @@ might increase the search time. This setting is off by default. Users must opt-i
[float]
[[kibana-siem-settings]]
-==== SIEM
+==== Security Solution
[horizontal]
-`siem:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the SIEM app.
-`siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events.
-`siem:ipReputationLinks`:: A JSON array containing links for verifying the reputation of an IP address. The links are displayed on
-{security-guide}/siem-ui-overview.html#network-ui[IP detail] pages.
-`siem:enableNewsFeed`:: Enables the security news feed on the SIEM *Overview*
+`securitySolution:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the Security app.
+`securitySolution:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the Security app collects events.
+`securitySolution:ipReputationLinks`:: A JSON array containing links for verifying the reputation of an IP address. The links are displayed on
+{security-guide}/network-page-overview.html[IP detail] pages.
+`securitySolution:enableNewsFeed`:: Enables the security news feed on the Security *Overview*
page.
-`siem:newsFeedUrl`:: The URL from which the security news feed content is
+`securitySolution:newsFeedUrl`:: The URL from which the security news feed content is
retrieved.
-`siem:refreshIntervalDefaults`:: The default refresh interval for the SIEM time filter, in milliseconds.
-`siem:timeDefaults`:: The default period of time in the SIEM time filter.
+`securitySolution:refreshIntervalDefaults`:: The default refresh interval for the Security time filter, in milliseconds.
+`securitySolution:timeDefaults`:: The default period of time in the Security time filter.
[float]
[[kibana-timelion-settings]]
diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc
index b31ae76d28052..0b6f94e86a39f 100644
--- a/docs/settings/reporting-settings.asciidoc
+++ b/docs/settings/reporting-settings.asciidoc
@@ -129,7 +129,7 @@ control the capturing process.
| Specify how long to allow the Reporting browser to wait for the "Loading..." screen
to dismiss and find the initial data for the Kibana page. If the time is
exceeded, a page screenshot is captured showing the current state, and the download link shows a warning message.
- Defaults to `30000` (30 seconds).
+ Defaults to `60000` (1 minute).
| `xpack.reporting.capture.timeouts.waitForElements`
| Specify how long to allow the Reporting browser to wait for all visualization
diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc
index dc7f585f3e4c3..a0995cab984d4 100644
--- a/docs/settings/security-settings.asciidoc
+++ b/docs/settings/security-settings.asciidoc
@@ -96,16 +96,13 @@ The valid settings in the `xpack.security.authc.providers` namespace vary depend
`..showInSelector`
| Flag that indicates if the provider should have an entry on the Login Selector UI. Setting this to `false` doesn't remove the provider from the authentication chain.
-|===
-
+2+a|
+[TIP]
[NOTE]
============
You are unable to set this setting to `false` for `basic` and `token` authentication providers.
============
-[cols="2*<"]
-|===
-
| `xpack.security.authc.providers.`
`..accessAgreement.message`
| Access agreement text in Markdown format. For more information, refer to <>.
@@ -125,8 +122,8 @@ In addition to <.maxRedirectURLSize`
-| The maximum size of the URL that {kib} is allowed to store during the authentication SAML handshake. For more information, refer to <>.
+`saml..useRelayStateDeepLink`
+| Determines if the provider should treat the `RelayState` parameter as a deep link in {kib} during Identity Provider initiated log in. By default, this setting is set to `false`. The link specified in `RelayState` should be a relative, URL-encoded {kib} URL. For example, the `/app/dashboards#/list` link in `RelayState` parameter would look like this: `RelayState=%2Fapp%2Fdashboards%23%2Flist`.
|===
@@ -164,19 +161,27 @@ There is a very limited set of cases when you'd want to change these settings. F
|===
[float]
-[[login-selector-settings]]
-===== Login Selector UI settings
+[[login-ui-settings]]
+===== Login user interface settings
+
+You can configure the following settings in the `kibana.yml` file.
[cols="2*<"]
|===
+| `xpack.security.loginAssistanceMessage`
+| Adds a message to the login UI. Useful for displaying information about maintenance windows, links to corporate sign up pages, and so on.
+
+| `xpack.security.loginHelp`
+| Adds a message accessible at the login UI with additional help information for the login process.
+
| `xpack.security.authc.selector.enabled`
-| Determines if the Login Selector UI should be enabled. By default, this setting is set to `true` if more than one authentication provider is configured.
+| Determines if the login selector UI should be enabled. By default, this setting is set to `true` if more than one authentication provider is configured.
|===
[float]
-[[security-ui-settings]]
-==== User interface security settings
+[[security-session-and-cookie-settings]]
+==== Session and cookie security settings
You can configure the following settings in the `kibana.yml` file.
@@ -186,8 +191,7 @@ You can configure the following settings in the `kibana.yml` file.
| Sets the name of the cookie used for the session. The default value is `"sid"`.
| `xpack.security.encryptionKey`
- | An arbitrary string of 32 characters or more that is used to encrypt credentials
- in a cookie. It is crucial that this key is not exposed to users of {kib}. By
+ | An arbitrary string of 32 characters or more that is used to encrypt session information. Do **not** expose this key to users of {kib}. By
default, a value is automatically generated in memory. If you use that default
behavior, all sessions are invalidated when {kib} restarts.
In addition, high-availability deployments of {kib} will behave unexpectedly
@@ -205,42 +209,33 @@ You can configure the following settings in the `kibana.yml` file.
This is *not set* by default, which modern browsers will treat as `Lax`. If you use Kibana embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting `xpack.security.secureCookies: true`.
| `xpack.security.session.idleTimeout`
- | Sets the session duration. By default, sessions stay active until the
- browser is closed. When this is set to an explicit idle timeout, closing the
- browser still requires the user to log back in to {kib}.
-
-|===
+ | Ensures that user sessions will expire after a period of inactivity. This and `xpack.security.session.lifespan` are both
+highly recommended. By default, this setting is not set.
+2+a|
[TIP]
============
-The format is a string of `[ms|s|m|h|d|w|M|Y]`
-(e.g. '70ms', '5s', '3d', '1Y').
+The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w').
============
-[cols="2*<"]
-|===
-
| `xpack.security.session.lifespan`
- | Sets the maximum duration, also known as "absolute timeout". By default,
- a session can be renewed indefinitely. When this value is set, a session will end
- once its lifespan is exceeded, even if the user is not idle. NOTE: if `idleTimeout`
- is not set, this setting will still cause sessions to expire.
-
-|===
+ | Ensures that user sessions will expire after the defined time period. This behavior also known as an "absolute timeout". If
+this is _not_ set, user sessions could stay active indefinitely. This and `xpack.security.session.idleTimeout` are both highly
+recommended. By default, this setting is not set.
+2+a|
[TIP]
============
-The format is a
-string of `[ms|s|m|h|d|w|M|Y]` (e.g. '70ms', '5s', '3d', '1Y').
+The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w').
============
-[cols="2*<"]
-|===
+| `xpack.security.session.cleanupInterval`
+| Sets the interval at which {kib} tries to remove expired and invalid sessions from the session index. By default, this value is 1 hour. The minimum value is 10 seconds.
-| `xpack.security.loginAssistanceMessage`
- | Adds a message to the login screen. Useful for displaying information about maintenance windows, links to corporate sign up pages etc.
-
-| `xpack.security.loginHelp`
- | Adds a message accessible at the Login Selector UI with additional help information for the login process.
+2+a|
+[TIP]
+============
+The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w').
+============
|===
diff --git a/docs/setup/production.asciidoc b/docs/setup/production.asciidoc
index 23dbb3346b7ef..3075220e3a47c 100644
--- a/docs/setup/production.asciidoc
+++ b/docs/setup/production.asciidoc
@@ -132,7 +132,7 @@ server.port
Settings that must be the same:
--------
-xpack.security.encryptionKey //decrypting session cookies
+xpack.security.encryptionKey //decrypting session information
xpack.reporting.encryptionKey //decrypting reports
xpack.encryptedSavedObjects.encryptionKey // decrypting saved objects
--------
diff --git a/docs/siem/images/cases-ui.png b/docs/siem/images/cases-ui.png
index d7b125b87a004..cb6361581d19e 100644
Binary files a/docs/siem/images/cases-ui.png and b/docs/siem/images/cases-ui.png differ
diff --git a/docs/siem/images/detections-ui.png b/docs/siem/images/detections-ui.png
index b698ac5d08469..b3fd7b5b51b8b 100644
Binary files a/docs/siem/images/detections-ui.png and b/docs/siem/images/detections-ui.png differ
diff --git a/docs/siem/images/hosts-ui.png b/docs/siem/images/hosts-ui.png
index 77cdb227e1d0b..57b09e340355e 100644
Binary files a/docs/siem/images/hosts-ui.png and b/docs/siem/images/hosts-ui.png differ
diff --git a/docs/siem/images/ml-ui.png b/docs/siem/images/ml-ui.png
index 568ae324dadd7..e301f6e28a45f 100644
Binary files a/docs/siem/images/ml-ui.png and b/docs/siem/images/ml-ui.png differ
diff --git a/docs/siem/images/network-ui.png b/docs/siem/images/network-ui.png
index 52caa7835d51a..a33040c41ddd3 100644
Binary files a/docs/siem/images/network-ui.png and b/docs/siem/images/network-ui.png differ
diff --git a/docs/siem/images/overview-ui.png b/docs/siem/images/overview-ui.png
index 09128775a5097..cf5475c89952e 100644
Binary files a/docs/siem/images/overview-ui.png and b/docs/siem/images/overview-ui.png differ
diff --git a/docs/siem/images/timeline-ui.png b/docs/siem/images/timeline-ui.png
index fbf5843fc445c..ad1794c4b93c9 100644
Binary files a/docs/siem/images/timeline-ui.png and b/docs/siem/images/timeline-ui.png differ
diff --git a/docs/siem/index.asciidoc b/docs/siem/index.asciidoc
index ceb4ac2bf1f34..18895f0533fd7 100644
--- a/docs/siem/index.asciidoc
+++ b/docs/siem/index.asciidoc
@@ -1,19 +1,22 @@
[role="xpack"]
[[xpack-siem]]
-= SIEM
+= Elastic Security
[partintro]
--
-The SIEM app in Kibana provides an interactive workspace for security teams to
-triage events and perform initial investigations. It enables analysis of
-host-related and network-related security events as part of alert investigations
-or interactive threat hunting.
+Elastic Security combines SIEM threat detection features with endpoint
+prevention and response capabilities in one solution, including:
+* A detection engine to identify attacks and system misconfiguration
+* 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
[role="screenshot"]
-image::siem/images/overview-ui.png[SIEM Overview in Kibana]
-
+image::siem/images/overview-ui.png[Elastic Security in Kibana]
[float]
== Add data
@@ -31,15 +34,14 @@ 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.
-The default index patterns for SIEM events are `auditbeat-*`, `winlogbeat-*`,
-`filebeat-*`, `packetbeat-*`, `endgame-*`, and `apm-*-transaction*`. You can
-change the default index patterns in
-*Kibana > Management > Advanced Settings > siem:defaultIndex*.
+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*.
[float]
-=== Elastic Endpoint Sensor Management Platform
+=== Elastic Security endpoint agent
-The Elastic Endpoint Sensor Management Platform (SMP) ships host and network events directly to the SIEM application, and is fully ECS compliant.
+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
@@ -49,7 +51,7 @@ 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.
-SIEM can ingest and normalize events from ECS-compatible data sources.
+Elastic Security can ingest and normalize events from ECS-compatible data sources.
--
diff --git a/docs/siem/machine-learning.asciidoc b/docs/siem/machine-learning.asciidoc
index baaa789cccd7e..c56332f1183ce 100644
--- a/docs/siem/machine-learning.asciidoc
+++ b/docs/siem/machine-learning.asciidoc
@@ -3,14 +3,12 @@
== Anomaly Detection with Machine Learning
For *{ess-trial}[Free Trial]*
-and *https://www.elastic.co/subscriptions[Platinum License]* deployments,
-Machine Learning functionality is available throughout the SIEM app. You can
-view the details of detected anomalies within the `Anomalies` table widget
-shown on the Hosts, Network and associated Details pages, or even narrow to
-the specific daterange of an anomaly from the `Max Anomaly Score` details in
-the overview of the Host and IP Details pages. Each of these interfaces also
-offer the ability to drag and drop details of the anomaly to Timeline, such
-as the `Entity` itself, or any of the associated `Influencers`.
+and *https://www.elastic.co/subscriptions[Platinum subscription]* deployments,
+Machine Learning functionality is available throughout Elastic Security. You can
+view the details of detected anomalies in the `Anomalies` table
+shown on the Hosts, Network and associated details pages. You can drag and drop
+anomaly details to Timeline, such as the `Entity` itself, or any of the
+associated `Influencers`.
[role="screenshot"]
image::siem/images/ml-ui.png[Machine Learning - Max Anomaly Score]
diff --git a/docs/siem/siem-ui.asciidoc b/docs/siem/siem-ui.asciidoc
index 1caa13dc6c903..98f8bc218aa76 100644
--- a/docs/siem/siem-ui.asciidoc
+++ b/docs/siem/siem-ui.asciidoc
@@ -1,20 +1,20 @@
[role="xpack"]
[[siem-ui]]
-== Using the SIEM UI
+== Using Elastic Security
-The SIEM app is a highly interactive workspace for security analysts. It is
-designed to be discoverable, clickable, draggable and droppable, expandable and
-collapsible, resizable, moveable, and so forth. You start with an overview. Then
-you can use the interactive UI to drill down into areas of interest.
+Elastic Security is a highly interactive workspace designed for security
+analysts. It provides a clear overview of events and alerts from your
+environment, and you can use the interactive UI to drill down into areas of
+interest.
[float]
[[hosts-ui]]
=== Hosts
-The Hosts view provides key metrics regarding host-related security events, and
-data tables and widgets that let you interact with the Timeline Event Viewer.
+The Hosts page provides key metrics regarding host-related security events, and
+data tables and histograms that let you interact with the Timeline Event Viewer.
You can drill down for deeper insights, and drag and drop items of interest from
-the Hosts view tables to Timeline for further investigation.
+the Hosts page to Timeline for further investigation.
[role="screenshot"]
image::siem/images/hosts-ui.png[]
@@ -24,11 +24,8 @@ image::siem/images/hosts-ui.png[]
[[network-ui]]
=== Network
-The Network view provides key network activity metrics, facilitates
-investigation time enrichment, and provides network event tables that enable
-interaction with the Timeline. You can drill down for deeper insights, and drag
-and drop items of interest from the Network view to Timeline for further
-investigation.
+The Network page displays key network activity metrics in an interactive map,
+and provides network event tables that enable interaction with Timeline.
[role="screenshot"]
image::siem/images/network-ui.png[]
@@ -38,14 +35,13 @@ image::siem/images/network-ui.png[]
=== Detections (beta)
The Detections feature automatically searches for threats and creates
-signals when they are detected. Signal detection rules define the conditions
-for creating signals. The SIEM app comes with prebuilt rules that search for
-suspicious activity on your network and hosts. Additionally, you can
+alerts when they are detected. Detection rules define the conditions
+for when alerts are created. Elastic Security comes with prebuilt rules that
+search for suspicious activity on your network and hosts. Additionally, you can
create your own rules.
-See {security-guide}/detection-engine-overview.html[Detections] in the SIEM
-Guide for information on managing detection rules and signals via the UI
-or the Detections API.
+See {security-guide}/detection-engine-overview.html[Detections] for information
+on managing detection rules and alerts.
[role="screenshot"]
image::siem/images/detections-ui.png[]
@@ -54,14 +50,14 @@ image::siem/images/detections-ui.png[]
[[cases-ui]]
=== Cases (beta)
-Cases are used to open and track security issues directly in SIEM.
+Cases are used to open and track security issues directly in Elastic Security.
Cases list the original reporter and all users who contribute to a case
(`participants`). Case comments support Markdown syntax, and allow linking to
saved Timelines. Additionally, you can send cases to external systems from
-within SIEM (currently ServiceNow and Jira).
+within Elastic Security.
For information about opening, updating, and closing cases, see
-{security-guide}/cases-overview.html[Cases] in the SIEM Guide.
+{security-guide}/cases-overview.html[Cases] in the Elastic Security Guide.
[role="screenshot"]
image::siem/images/cases-ui.png[]
@@ -73,31 +69,31 @@ image::siem/images/cases-ui.png[]
Timeline is your workspace for threat hunting and alert investigations.
[role="screenshot"]
-image::siem/images/timeline-ui.png[SIEM Timeline]
+image::siem/images/timeline-ui.png[Elastic Security Timeline]
You can drag objects of interest into the Timeline Event Viewer to create
exactly the query filter you need. You can drag items from table widgets within
Hosts and Network pages, or even from within Timeline itself.
-A timeline is responsive and persists as you move through the SIEM app
+A timeline is responsive and persists as you move through Elastic Security
collecting data.
-See the {security-guide}[Security Guide] for more details on data sources and an
-overview of UI elements and capabilities.
+For detailed information about Timeline, see
+{security-guide}/timelines-ui.html[Investigating events in Timeline].
[float]
[[sample-workflow]]
=== Sample workflow
An analyst notices a suspicious user ID that warrants further investigation, and
-clicks a url that links to the SIEM app.
+clicks a URL that links to Elastic Security.
-The analyst uses the tables, widgets, and filtering and search capabilities in
-the SIEM app to get to the bottom of the alert. The analyst can drag items of
-interest to the timeline for further analysis.
+The analyst uses the tables, histograms, and filtering and search capabilities in
+Elastic Security to get to the bottom of the alert. The analyst can drag items of
+interest to Timeline for further analysis.
-Within the timeline, the analyst can investigate further--drilling down,
-searching, and filtering--and add notes and pin items of interest.
+Within Timeline, the analyst can investigate further - drilling down,
+searching, and filtering - and add notes and pin items of interest.
The analyst can name the timeline, write summary notes, and share it with others
if appropriate.
diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc
index fe93e38151b82..bd37351e9b60a 100644
--- a/docs/user/security/authentication/index.asciidoc
+++ b/docs/user/security/authentication/index.asciidoc
@@ -59,8 +59,6 @@ For more information, refer to <>.
-[float]
-[[security-saml-and-long-urls]]
-===== SAML and long URLs
-
-At the beginning of the SAML handshake, {kib} stores the initial URL in the session cookie, so it can redirect the user back to that URL after successful SAML authentication.
-If the URL is long, the session cookie might exceed the maximum size supported by the browser--typically 4KB for all cookies per domain. When this happens, the session cookie is truncated,
-or dropped completely, and you might experience sporadic failures during SAML authentication.
-
-To remedy this issue, you can decrease the maximum
-size of the URL that {kib} is allowed to store during the SAML handshake. The default value is 2KB.
-
-[source,yaml]
---------------------------------------------------------------------------------
-xpack.security.authc.providers:
- saml.saml1:
- order: 0
- realm: saml1
- maxRedirectURLSize: 1kb
---------------------------------------------------------------------------------
-
[[oidc]]
==== OpenID Connect single sign-on
@@ -263,11 +239,11 @@ The following sections apply both to <> and <>
===== Access and refresh tokens
Once the user logs in to {kib} Single Sign-On, either using SAML or OpenID Connect, {es} issues access and refresh tokens
-that {kib} encrypts and stores them in its own session cookie. This way, the user isn't redirected to the Identity Provider
-for every request that requires authentication. It also means that the {kib} session depends on the <> settings, and the user is automatically logged
-out if the session expires. An access token that is stored in the session cookie can expire, in which case {kib} will
-automatically renew it with a one-time-use refresh token and store it in the same cookie.
+out if the session expires. An access token that is stored in the session can expire, in which case {kib} will
+automatically renew it with a one-time-use refresh token and store it in the same session.
{kib} can only determine if an access token has expired if it receives a request that requires authentication. If both access
and refresh tokens have already expired (for example, after 24 hours of inactivity), {kib} initiates a new "handshake" and
@@ -280,8 +256,7 @@ indicates that both access and refresh tokens are expired. Reloading the current
[float]
===== Local and global logout
-During logout, both the {kib} session cookie and access/refresh token pair are invalidated. Even if the cookie has been
-leaked, it can't be re-used after logout. This is known as "local" logout.
+During logout, both the {kib} session and {es} access/refresh token pair are invalidated. This is known as "local" logout.
{kib} can also initiate a "global" logout or _Single Logout_ if it's supported by the external authentication provider and not
explicitly disabled by {es}. In this case, the user is redirected to the external authentication provider for log out of
diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc
index 0177ac94bd402..0f02279eaf1f3 100644
--- a/docs/user/security/securing-kibana.asciidoc
+++ b/docs/user/security/securing-kibana.asciidoc
@@ -56,34 +56,16 @@ xpack.security.encryptionKey: "something_at_least_32_characters"
For more information, see <>.
--
-. Optional: Set a timeout to expire idle sessions. By default, a session stays
-active until the browser is closed. To define a sliding session expiration, set
-the `xpack.security.session.idleTimeout` property in the `kibana.yml`
-configuration file. The idle timeout is formatted as a duration of
-`[ms|s|m|h|d|w|M|Y]` (e.g. '70ms', '5s', '3d', '1Y'). For example, set
-the idle timeout to expire idle sessions after 10 minutes:
+. Configure {kib}'s session expiration settings. Set both the idle timeout and lifespan settings:
+
--
[source,yaml]
--------------------------------------------------------------------------------
-xpack.security.session.idleTimeout: "10m"
+xpack.security.session.idleTimeout: "1h"
+xpack.security.session.lifespan: "30d"
--------------------------------------------------------------------------------
---
-. Optional: Change the maximum session duration or "lifespan" -- also known as
-the "absolute timeout". By default, a session stays active until the browser is
-closed. If an idle timeout is defined, a session can still be extended
-indefinitely. To define a maximum session lifespan, set the
-`xpack.security.session.lifespan` property in the `kibana.yml` configuration
-file. The lifespan is formatted as a duration of `[ms|s|m|h|d|w|M|Y]`
-(e.g. '70ms', '5s', '3d', '1Y'). For example, set the lifespan to expire
-sessions after 8 hours:
-+
---
-[source,yaml]
---------------------------------------------------------------------------------
-xpack.security.session.lifespan: "8h"
---------------------------------------------------------------------------------
+For more information, see <>.
--
. Optional: <>.
@@ -146,3 +128,4 @@ include::securing-communications/index.asciidoc[]
include::securing-communications/elasticsearch-mutual-tls.asciidoc[]
include::audit-logging.asciidoc[]
include::access-agreement.asciidoc[]
+include::session-management.asciidoc[]
diff --git a/docs/user/security/session-management.asciidoc b/docs/user/security/session-management.asciidoc
new file mode 100644
index 0000000000000..0df5b3b31a203
--- /dev/null
+++ b/docs/user/security/session-management.asciidoc
@@ -0,0 +1,49 @@
+[role="xpack"]
+[[xpack-security-session-management]]
+=== Session management
+
+When you log in, {kib} creates a session that is used to authenticate subsequent requests to {kib}. A session consists of two components: an encrypted cookie that is stored in your browser, and an encrypted document in a dedicated {es} hidden index. By default, the name of that index is `.kibana_security_session_1`, where the prefix is derived from the primary `.kibana` index. If either of these components are missing, the session is no longer valid.
+
+When your session expires, or you log out, {kib} will invalidate your cookie and remove session information from the index. {kib} also periodically invalidates and removes any expired sessions that weren't explicitly invalidated.
+
+[[session-idle-timeout]]
+==== Session idle timeout
+
+You can use `xpack.security.session.idleTimeout` to expire sessions after a period of inactivity. This and `xpack.security.session.lifespan` are both highly recommended.
+By default, sessions don't expire because of inactivity. To define a sliding session expiration, set the property in the `kibana.yml` configuration file. The idle timeout is formatted as a duration of `[ms|s|m|h|d|w|M|Y]` (e.g. '20m', '24h', '7d', '1w'). For example, set the idle timeout to expire sessions after 1 hour of inactivity:
+
+--
+[source,yaml]
+--------------------------------------------------------------------------------
+xpack.security.session.idleTimeout: "1h"
+--------------------------------------------------------------------------------
+--
+
+[[session-lifespan]]
+==== Session lifespan
+
+You can use `xpack.security.session.lifespan` to configure the maximum session duration or "lifespan" -- also known as the "absolute timeout". This and `xpack.security.session.idleTimeout` are both highly recommended. By default, sessions don't have a fixed lifespan, and if an idle timeout is defined, a session can still be extended indefinitely. To define a maximum session lifespan, set the property in the `kibana.yml` configuration file. The lifespan is formatted as a duration of `[ms|s|m|h|d|w|M|Y]` (e.g. '20m', '24h', '7d', '1w'). For example, set the lifespan to expire sessions after 30 days:
+
+--
+[source,yaml]
+--------------------------------------------------------------------------------
+xpack.security.session.lifespan: "30d"
+--------------------------------------------------------------------------------
+--
+
+[[session-cleanup-interval]]
+==== Session cleanup interval
+
+[IMPORTANT]
+============================================================================
+If you specify neither session idle timeout nor lifespan, then {kib} will not automatically remove session information from the index unless you explicitly log out. This might lead to an infinitely growing session index. Configure the idle timeout and lifespan settings for the {kib} sessions so that they can be cleaned up even if you don't explicitly log out.
+============================================================================
+
+You can configure the interval at which {kib} tries to remove expired and invalid sessions from the session index. By default, this value is 1 hour and cannot be less than 10 seconds. To define another interval, set the `xpack.security.session.cleanupInterval` property in the `kibana.yml` configuration file. The interval is formatted as a duration of `[ms|s|m|h|d|w|M|Y]` (e.g. '20m', '24h', '7d', '1w'). For example, schedule the session index cleanup to perform once a day:
+
+--
+[source,yaml]
+--------------------------------------------------------------------------------
+xpack.security.session.cleanupInterval: "1d"
+--------------------------------------------------------------------------------
+--
diff --git a/examples/embeddable_examples/public/book/add_book_to_library_action.tsx b/examples/embeddable_examples/public/book/add_book_to_library_action.tsx
index b74a1d5642982..4ae3a545df0d0 100644
--- a/examples/embeddable_examples/public/book/add_book_to_library_action.tsx
+++ b/examples/embeddable_examples/public/book/add_book_to_library_action.tsx
@@ -21,6 +21,7 @@ import { i18n } from '@kbn/i18n';
import { createAction, IncompatibleActionError } from '../../../../src/plugins/ui_actions/public';
import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
import { ViewMode, isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public';
+import { DASHBOARD_CONTAINER_TYPE } from '../../../../src/plugins/dashboard/public';
interface ActionContext {
embeddable: BookEmbeddable;
@@ -41,6 +42,8 @@ export const createAddBookToLibraryAction = () =>
return (
embeddable.type === BOOK_EMBEDDABLE &&
embeddable.getInput().viewMode === ViewMode.EDIT &&
+ embeddable.getRoot().isContainer &&
+ embeddable.getRoot().type !== DASHBOARD_CONTAINER_TYPE &&
isReferenceOrValueEmbeddable(embeddable) &&
!embeddable.inputIsRefType(embeddable.getInput())
);
diff --git a/examples/embeddable_examples/public/book/book_embeddable.tsx b/examples/embeddable_examples/public/book/book_embeddable.tsx
index dd9418c0e8596..73b1629d985b7 100644
--- a/examples/embeddable_examples/public/book/book_embeddable.tsx
+++ b/examples/embeddable_examples/public/book/book_embeddable.tsx
@@ -26,6 +26,7 @@ import {
EmbeddableOutput,
SavedObjectEmbeddableInput,
ReferenceOrValueEmbeddable,
+ Container,
} from '../../../../src/plugins/embeddable/public';
import { BookSavedObjectAttributes } from '../../common';
import { BookEmbeddableComponent } from './book_component';
@@ -103,7 +104,12 @@ export class BookEmbeddable extends Embeddable => {
- return this.attributeService.getInputAsValueType(this.input);
+ const input =
+ this.getRoot() && (this.getRoot() as Container).getInput().panels[this.id].explicitInput
+ ? ((this.getRoot() as Container).getInput().panels[this.id]
+ .explicitInput as BookEmbeddableInput)
+ : this.input;
+ return this.attributeService.getInputAsValueType(input);
};
getInputAsRefType = async (): Promise => {
diff --git a/examples/embeddable_examples/public/book/unlink_book_from_library_action.tsx b/examples/embeddable_examples/public/book/unlink_book_from_library_action.tsx
index cef77092a642a..ebab2c483c625 100644
--- a/examples/embeddable_examples/public/book/unlink_book_from_library_action.tsx
+++ b/examples/embeddable_examples/public/book/unlink_book_from_library_action.tsx
@@ -21,6 +21,7 @@ import { i18n } from '@kbn/i18n';
import { createAction, IncompatibleActionError } from '../../../../src/plugins/ui_actions/public';
import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
import { ViewMode, isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public';
+import { DASHBOARD_CONTAINER_TYPE } from '../../../../src/plugins/dashboard/public';
interface ActionContext {
embeddable: BookEmbeddable;
@@ -41,6 +42,8 @@ export const createUnlinkBookFromLibraryAction = () =>
return (
embeddable.type === BOOK_EMBEDDABLE &&
embeddable.getInput().viewMode === ViewMode.EDIT &&
+ embeddable.getRoot().isContainer &&
+ embeddable.getRoot().type !== DASHBOARD_CONTAINER_TYPE &&
isReferenceOrValueEmbeddable(embeddable) &&
embeddable.inputIsRefType(embeddable.getInput())
);
diff --git a/examples/ui_actions_explorer/public/actions/actions.tsx b/examples/ui_actions_explorer/public/actions/actions.tsx
index 6d83362e998bc..777bcd9c18119 100644
--- a/examples/ui_actions_explorer/public/actions/actions.tsx
+++ b/examples/ui_actions_explorer/public/actions/actions.tsx
@@ -21,7 +21,11 @@ import { OverlayStart } from 'kibana/public';
import { EuiFieldText, EuiModalBody, EuiButton } from '@elastic/eui';
import { useState } from 'react';
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
-import { createAction, UiActionsStart } from '../../../../src/plugins/ui_actions/public';
+import {
+ ActionExecutionContext,
+ createAction,
+ UiActionsStart,
+} from '../../../../src/plugins/ui_actions/public';
export const USER_TRIGGER = 'USER_TRIGGER';
export const COUNTRY_TRIGGER = 'COUNTRY_TRIGGER';
@@ -37,7 +41,8 @@ export const ACTION_SHOWCASE_PLUGGABILITY = 'ACTION_SHOWCASE_PLUGGABILITY';
export const showcasePluggability = createAction({
type: ACTION_SHOWCASE_PLUGGABILITY,
getDisplayName: () => 'This is pluggable! Any plugin can inject their actions here.',
- execute: async () => alert("Isn't that cool?!"),
+ execute: async (context: ActionExecutionContext) =>
+ alert(`Isn't that cool?! Triggered by ${context.trigger?.id} trigger`),
});
export interface PhoneContext {
diff --git a/examples/ui_actions_explorer/public/app.tsx b/examples/ui_actions_explorer/public/app.tsx
index 1b0667962a3c2..d59309f006838 100644
--- a/examples/ui_actions_explorer/public/app.tsx
+++ b/examples/ui_actions_explorer/public/app.tsx
@@ -97,9 +97,9 @@ const ActionsExplorer = ({ uiActionsApi, openModal }: Props) => {
});
uiActionsApi.addTriggerAction(HELLO_WORLD_TRIGGER_ID, dynamicAction);
setConfirmationText(
- `You've successfully added a new action: ${dynamicAction.getDisplayName(
- {}
- )}. Refresh the page to reset state. It's up to the user of the system to persist state like this.`
+ `You've successfully added a new action: ${dynamicAction.getDisplayName({
+ trigger: uiActionsApi.getTrigger(HELLO_WORLD_TRIGGER_ID),
+ })}. Refresh the page to reset state. It's up to the user of the system to persist state like this.`
);
}}
>
diff --git a/package.json b/package.json
index df35e5901159b..d6b272e85bd20 100644
--- a/package.json
+++ b/package.json
@@ -84,12 +84,14 @@
"**/@types/angular": "^1.6.56",
"**/@types/hoist-non-react-statics": "^3.3.1",
"**/@types/chai": "^4.2.11",
- "**/cypress/@types/lodash": "^4.14.155",
+ "**/cypress/@types/lodash": "^4.14.159",
+ "**/cypress/lodash": "^4.17.20",
"**/typescript": "3.9.5",
"**/graphql-toolkit/lodash": "^4.17.15",
"**/hoist-non-react-statics": "^3.3.2",
"**/isomorphic-git/**/base64-js": "^1.2.1",
"**/image-diff/gm/debug": "^2.6.9",
+ "**/load-grunt-config/lodash": "^4.17.20",
"**/react-dom": "^16.12.0",
"**/react": "^16.12.0",
"**/react-test-renderer": "^16.12.0",
@@ -124,7 +126,7 @@
"@elastic/datemath": "5.0.3",
"@elastic/elasticsearch": "7.9.0-rc.2",
"@elastic/ems-client": "7.9.3",
- "@elastic/eui": "26.3.1",
+ "@elastic/eui": "27.4.0",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "8.1.1-kibana2",
"@elastic/numeral": "^2.5.0",
@@ -205,7 +207,7 @@
"leaflet-vega": "^0.8.6",
"leaflet.heat": "0.2.0",
"less": "npm:@elastic/less@2.7.3-kibana",
- "lodash": "^4.17.15",
+ "lodash": "^4.17.20",
"lru-cache": "4.1.5",
"markdown-it": "^10.0.0",
"minimatch": "^3.0.4",
@@ -332,7 +334,7 @@
"@types/json5": "^0.0.30",
"@types/license-checker": "15.0.0",
"@types/listr": "^0.14.0",
- "@types/lodash": "^4.14.155",
+ "@types/lodash": "^4.14.159",
"@types/lru-cache": "^5.1.0",
"@types/markdown-it": "^0.0.7",
"@types/minimatch": "^2.0.29",
@@ -477,7 +479,7 @@
"zlib": "^1.0.5"
},
"engines": {
- "node": "10.21.0",
+ "node": "10.22.0",
"yarn": "^1.21.1"
}
}
diff --git a/packages/kbn-monaco/src/xjson/grammar.ts b/packages/kbn-monaco/src/xjson/grammar.ts
index fbd7b3d319c1d..e6599837dbb2e 100644
--- a/packages/kbn-monaco/src/xjson/grammar.ts
+++ b/packages/kbn-monaco/src/xjson/grammar.ts
@@ -22,6 +22,19 @@ export enum AnnoTypes {
warning = 'warning',
}
+export type Parser = ReturnType;
+
+export interface Annotation {
+ name?: string;
+ type: AnnoTypes;
+ text: string;
+ at: number;
+}
+
+export interface ParseResult {
+ annotations: Annotation[];
+}
+
/* eslint-disable */
export const createParser = () => {
diff --git a/packages/kbn-monaco/src/xjson/index.ts b/packages/kbn-monaco/src/xjson/index.ts
index 35fd35887bc56..8a4644a3792d2 100644
--- a/packages/kbn-monaco/src/xjson/index.ts
+++ b/packages/kbn-monaco/src/xjson/index.ts
@@ -17,8 +17,10 @@
* under the License.
*/
-import { registerGrammarChecker } from './language';
-
+/**
+ * This import registers the XJSON monaco language contribution
+ */
+import './language';
import { ID } from './constants';
-export const XJsonLang = { registerGrammarChecker, ID };
+export const XJsonLang = { ID };
diff --git a/packages/kbn-monaco/src/xjson/language.ts b/packages/kbn-monaco/src/xjson/language.ts
index 54b7004fecd8e..4ae7f2402ed2f 100644
--- a/packages/kbn-monaco/src/xjson/language.ts
+++ b/packages/kbn-monaco/src/xjson/language.ts
@@ -32,13 +32,16 @@ const wps = new WorkerProxyService();
registerLexerRules(monaco);
// In future we will need to make this map languages to workers using "id" and/or "label" values
-// that get passed in.
+// that get passed in. Also this should not live inside the "xjson" dir directly. We can update this
+// once we have another worker.
// @ts-ignore
window.MonacoEnvironment = {
- getWorker: (id: any, label: any) => {
- // In kibana we will probably build this once and then load with raw-loader
- const blob = new Blob([workerSrc], { type: 'application/javascript' });
- return new Worker(URL.createObjectURL(blob));
+ getWorker: (module: string, languageId: string) => {
+ if (languageId === ID) {
+ // In kibana we will probably build this once and then load with raw-loader
+ const blob = new Blob([workerSrc], { type: 'application/javascript' });
+ return new Worker(URL.createObjectURL(blob));
+ }
},
};
@@ -47,15 +50,19 @@ monaco.languages.onLanguage(ID, async () => {
});
const OWNER = 'XJSON_GRAMMAR_CHECKER';
-export const registerGrammarChecker = (editor: monaco.editor.IEditor) => {
+
+export const registerGrammarChecker = () => {
const allDisposables: monaco.IDisposable[] = [];
- const updateAnnos = async () => {
- const { annotations } = await wps.getAnnos();
- const model = editor.getModel() as monaco.editor.ITextModel | null;
- if (!model) {
+ const updateAnnotations = async (model: monaco.editor.IModel): Promise => {
+ if (model.isDisposed()) {
return;
}
+ const parseResult = await wps.getAnnos(model.uri);
+ if (!parseResult) {
+ return;
+ }
+ const { annotations } = parseResult;
monaco.editor.setModelMarkers(
model,
OWNER,
@@ -74,19 +81,21 @@ export const registerGrammarChecker = (editor: monaco.editor.IEditor) => {
};
const onModelAdd = (model: monaco.editor.IModel) => {
- allDisposables.push(
- model.onDidChangeContent(async () => {
- updateAnnos();
- })
- );
+ if (model.getModeId() === ID) {
+ allDisposables.push(
+ model.onDidChangeContent(async () => {
+ updateAnnotations(model);
+ })
+ );
- updateAnnos();
+ updateAnnotations(model);
+ }
};
-
allDisposables.push(monaco.editor.onDidCreateModel(onModelAdd));
- monaco.editor.getModels().forEach(onModelAdd);
return () => {
wps.stop();
allDisposables.forEach((d) => d.dispose());
};
};
+
+registerGrammarChecker();
diff --git a/packages/kbn-monaco/src/xjson/worker/xjson_worker.ts b/packages/kbn-monaco/src/xjson/worker/xjson_worker.ts
index 501adcacb6990..c99f033d793a8 100644
--- a/packages/kbn-monaco/src/xjson/worker/xjson_worker.ts
+++ b/packages/kbn-monaco/src/xjson/worker/xjson_worker.ts
@@ -19,17 +19,19 @@
/* eslint-disable-next-line @kbn/eslint/module_migration */
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
-import { createParser } from '../grammar';
+import { createParser, Parser, ParseResult } from '../grammar';
export class XJsonWorker {
constructor(private ctx: monaco.worker.IWorkerContext) {}
- private parser: any;
+ private parser: Parser | undefined;
- async parse() {
+ async parse(modelUri: string): Promise {
if (!this.parser) {
this.parser = createParser();
}
- const [model] = this.ctx.getMirrorModels();
- return this.parser(model.getValue());
+ const model = this.ctx.getMirrorModels().find((m) => m.uri.toString() === modelUri);
+ if (model) {
+ return this.parser(model.getValue());
+ }
}
}
diff --git a/packages/kbn-monaco/src/xjson/worker_proxy_service.ts b/packages/kbn-monaco/src/xjson/worker_proxy_service.ts
index 17d6d56e51e59..548a413a483d9 100644
--- a/packages/kbn-monaco/src/xjson/worker_proxy_service.ts
+++ b/packages/kbn-monaco/src/xjson/worker_proxy_service.ts
@@ -17,32 +17,21 @@
* under the License.
*/
-import { AnnoTypes } from './grammar';
+import { ParseResult } from './grammar';
import { monaco } from '../monaco';
import { XJsonWorker } from './worker';
import { ID } from './constants';
-export interface Annotation {
- name?: string;
- type: AnnoTypes;
- text: string;
- at: number;
-}
-
-export interface AnnotationsResponse {
- annotations: Annotation[];
-}
-
export class WorkerProxyService {
private worker: monaco.editor.MonacoWebWorker | undefined;
- public async getAnnos(): Promise {
+ public async getAnnos(modelUri: monaco.Uri): Promise {
if (!this.worker) {
throw new Error('Worker Proxy Service has not been setup!');
}
- await this.worker.withSyncedResources(monaco.editor.getModels().map(({ uri }) => uri));
+ await this.worker.withSyncedResources([modelUri]);
const proxy = await this.worker.getProxy();
- return proxy.parse();
+ return proxy.parse(modelUri.toString());
}
public setup() {
diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json
index 78fa48979c1b5..6b36f14df95e9 100644
--- a/packages/kbn-pm/package.json
+++ b/packages/kbn-pm/package.json
@@ -22,7 +22,7 @@
"@types/glob": "^5.0.35",
"@types/globby": "^6.1.0",
"@types/has-ansi": "^3.0.0",
- "@types/lodash": "^4.14.155",
+ "@types/lodash": "^4.14.159",
"@types/log-symbols": "^2.0.0",
"@types/ncp": "^2.0.1",
"@types/node": ">=10.17.17 <10.20.0",
diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json
index f86bcfd2bb7b2..24655f8e57026 100644
--- a/packages/kbn-test/package.json
+++ b/packages/kbn-test/package.json
@@ -14,7 +14,7 @@
"@kbn/babel-preset": "1.0.0",
"@kbn/dev-utils": "1.0.0",
"@types/joi": "^13.4.2",
- "@types/lodash": "^4.14.155",
+ "@types/lodash": "^4.14.159",
"@types/parse-link-header": "^1.0.0",
"@types/strip-ansi": "^5.2.1",
"@types/xml2js": "^0.4.5",
diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json
index ae14777e8b44a..a37281cb2263f 100644
--- a/packages/kbn-ui-shared-deps/package.json
+++ b/packages/kbn-ui-shared-deps/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"@elastic/charts": "19.8.1",
- "@elastic/eui": "26.3.1",
+ "@elastic/eui": "27.4.0",
"@elastic/numeral": "^2.5.0",
"@kbn/i18n": "1.0.0",
"@kbn/monaco": "1.0.0",
diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js
index 3bc710e44f7bc..345156b2491a1 100644
--- a/src/cli/serve/serve.js
+++ b/src/cli/serve/serve.js
@@ -136,11 +136,6 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
if (opts.verbose) set('logging.verbose', true);
if (opts.logFile) set('logging.dest', opts.logFile);
- if (opts.optimize) {
- set('server.autoListen', false);
- set('plugins.initialize', false);
- }
-
set('plugins.scanDirs', _.compact([].concat(get('plugins.scanDirs'), opts.pluginDir)));
set(
'plugins.paths',
diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts
index 50e47bdf71772..d0c2ac111eb1f 100644
--- a/src/core/public/application/application_service.test.ts
+++ b/src/core/public/application/application_service.test.ts
@@ -117,7 +117,7 @@ describe('#setup()', () => {
expect.objectContaining({
id: 'app1',
legacy: false,
- navLinkStatus: AppNavLinkStatus.default,
+ navLinkStatus: AppNavLinkStatus.visible,
status: AppStatus.accessible,
})
);
@@ -125,7 +125,7 @@ describe('#setup()', () => {
expect.objectContaining({
id: 'app2',
legacy: false,
- navLinkStatus: AppNavLinkStatus.default,
+ navLinkStatus: AppNavLinkStatus.visible,
status: AppStatus.accessible,
})
);
@@ -142,7 +142,7 @@ describe('#setup()', () => {
expect.objectContaining({
id: 'app1',
legacy: false,
- navLinkStatus: AppNavLinkStatus.default,
+ navLinkStatus: AppNavLinkStatus.hidden,
status: AppStatus.inaccessible,
defaultPath: 'foo/bar',
tooltip: 'App inaccessible due to reason',
@@ -152,7 +152,7 @@ describe('#setup()', () => {
expect.objectContaining({
id: 'app2',
legacy: false,
- navLinkStatus: AppNavLinkStatus.default,
+ navLinkStatus: AppNavLinkStatus.visible,
status: AppStatus.accessible,
})
);
@@ -268,7 +268,7 @@ describe('#setup()', () => {
expect.objectContaining({
id: 'app2',
legacy: false,
- navLinkStatus: AppNavLinkStatus.default,
+ navLinkStatus: AppNavLinkStatus.visible,
status: AppStatus.accessible,
tooltip: 'App accessible',
})
@@ -523,7 +523,7 @@ describe('#start()', () => {
appRoute: '/app/app1',
id: 'app1',
legacy: false,
- navLinkStatus: AppNavLinkStatus.default,
+ navLinkStatus: AppNavLinkStatus.visible,
status: AppStatus.accessible,
})
);
@@ -532,7 +532,7 @@ describe('#start()', () => {
appUrl: '/my-url',
id: 'app2',
legacy: true,
- navLinkStatus: AppNavLinkStatus.default,
+ navLinkStatus: AppNavLinkStatus.visible,
status: AppStatus.accessible,
})
);
diff --git a/src/core/public/application/utils.test.ts b/src/core/public/application/utils.test.ts
index b41945aa43682..4663ca2db21e7 100644
--- a/src/core/public/application/utils.test.ts
+++ b/src/core/public/application/utils.test.ts
@@ -18,15 +18,15 @@
*/
import { of } from 'rxjs';
-import { LegacyApp, App, AppStatus, AppNavLinkStatus } from './types';
+import { App, AppNavLinkStatus, AppStatus, LegacyApp } from './types';
import { BasePath } from '../http/base_path';
import {
- removeSlashes,
appendAppPath,
+ getAppInfo,
isLegacyApp,
- relativeToAbsolute,
parseAppUrl,
- getAppInfo,
+ relativeToAbsolute,
+ removeSlashes,
} from './utils';
describe('removeSlashes', () => {
@@ -494,7 +494,7 @@ describe('getAppInfo', () => {
id: 'some-id',
title: 'some-title',
status: AppStatus.accessible,
- navLinkStatus: AppNavLinkStatus.default,
+ navLinkStatus: AppNavLinkStatus.visible,
appRoute: `/app/some-id`,
legacy: false,
});
@@ -509,8 +509,35 @@ describe('getAppInfo', () => {
id: 'some-id',
title: 'some-title',
status: AppStatus.accessible,
- navLinkStatus: AppNavLinkStatus.default,
+ navLinkStatus: AppNavLinkStatus.visible,
legacy: true,
});
});
+
+ it('computes the navLinkStatus depending on the app status', () => {
+ expect(
+ getAppInfo(
+ createApp({
+ navLinkStatus: AppNavLinkStatus.default,
+ status: AppStatus.inaccessible,
+ })
+ )
+ ).toEqual(
+ expect.objectContaining({
+ navLinkStatus: AppNavLinkStatus.hidden,
+ })
+ );
+ expect(
+ getAppInfo(
+ createApp({
+ navLinkStatus: AppNavLinkStatus.default,
+ status: AppStatus.accessible,
+ })
+ )
+ ).toEqual(
+ expect.objectContaining({
+ navLinkStatus: AppNavLinkStatus.visible,
+ })
+ );
+ });
});
diff --git a/src/core/public/application/utils.ts b/src/core/public/application/utils.ts
index 92d25fa468c4a..c5ed7b659f3ae 100644
--- a/src/core/public/application/utils.ts
+++ b/src/core/public/application/utils.ts
@@ -18,7 +18,15 @@
*/
import { IBasePath } from '../http';
-import { App, LegacyApp, PublicAppInfo, PublicLegacyAppInfo, ParsedAppUrl } from './types';
+import {
+ App,
+ AppNavLinkStatus,
+ AppStatus,
+ LegacyApp,
+ ParsedAppUrl,
+ PublicAppInfo,
+ PublicLegacyAppInfo,
+} from './types';
/**
* Utility to remove trailing, leading or duplicate slashes.
@@ -116,12 +124,18 @@ const removeBasePath = (url: string, basePath: IBasePath, origin: string): strin
};
export function getAppInfo(app: App | LegacyApp): PublicAppInfo | PublicLegacyAppInfo {
+ const navLinkStatus =
+ app.navLinkStatus === AppNavLinkStatus.default
+ ? app.status === AppStatus.inaccessible
+ ? AppNavLinkStatus.hidden
+ : AppNavLinkStatus.visible
+ : app.navLinkStatus!;
if (isLegacyApp(app)) {
const { updater$, ...infos } = app;
return {
...infos,
status: app.status!,
- navLinkStatus: app.navLinkStatus!,
+ navLinkStatus,
legacy: true,
};
} else {
@@ -129,7 +143,7 @@ export function getAppInfo(app: App | LegacyApp): PublicAppInfo | Publi
return {
...infos,
status: app.status!,
- navLinkStatus: app.navLinkStatus!,
+ navLinkStatus,
appRoute: app.appRoute!,
legacy: false,
};
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 9ecbc055e3320..72d62730fa698 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
@@ -378,7 +378,9 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
}
/>
@@ -404,260 +406,187 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+ class="euiAccordion__icon euiAccordion__icon-isOpen"
+ data-euiicon-type="arrowRight"
+ />
+
+
+
+
+ Kibana
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ class="euiFlexItem euiFlexItem--flexGrowZero"
+ >
+
+
+
+
+ Observability
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
-
- Management
-
+
+ Management
+
+
-
-
-
-
-
-
-
+
+
+
+
+
-
+
+ monitoring
+
+
+
+
+
-
-
-
-
-
-
-
-
-
- Dock navigation
-
-
-
-
-
+
+
+
+ Dock navigation
+
+
+
+
+
+
-
-
-
-
- close
+
+
+ close
+
-
-
-
-
- }
- onActivation={[Function]}
- onDeactivation={[Function]}
- persistentFocus={false}
- >
-
-
+
+
+ }
+ onActivation={[Function]}
+ onDeactivation={[Function]}
+ persistentFocus={false}
+ returnFocus={[Function]}
+ shards={Array []}
+ sideCar={
+ Object {
+ "assignMedium": [Function],
+ "assignSyncMedium": [Function],
+ "options": Object {
+ "async": true,
+ "ssr": false,
+ },
+ "read": [Function],
+ "useMedium": [Function],
+ }
+ }
+ >
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
- Recently viewed
-
+
+ Recently viewed
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Dock navigation
-
-
-
-
+
+
+ Dock navigation
+
+
+
+
+
-
-
-
-
- close
-
-
-
-
-
- }
- onActivation={[Function]}
- onDeactivation={[Function]}
- persistentFocus={false}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
- data-test-subj="collapsibleNavGroup-recentlyViewed"
- id="mockId"
- initialIsOpen={true}
- onToggle={[Function]}
- paddingSize="none"
- >
-
-
-
-
-
-
-
-
-
-
+
+ Custom link
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Kibana
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
- data-test-subj="collapsibleNavGroup-kibana"
- id="mockId"
- initialIsOpen={true}
- onToggle={[Function]}
- paddingSize="none"
- >
-
-
-
-
-
-
-
-
-
-
-
-
- Observability
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
- data-test-subj="collapsibleNavGroup-observability"
- id="mockId"
- initialIsOpen={true}
- onToggle={[Function]}
- paddingSize="none"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Security
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
- data-test-subj="collapsibleNavGroup-security"
- id="mockId"
- initialIsOpen={true}
- onToggle={[Function]}
- paddingSize="none"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Management
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
- data-test-subj="collapsibleNavGroup-management"
- id="mockId"
- initialIsOpen={true}
- onToggle={[Function]}
- paddingSize="none"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
Dock navigation
- ,
- }
- }
- color="subdued"
- data-test-subj="collapsible-nav-lock"
- iconType="lockOpen"
- label="Dock navigation"
- onClick={[Function]}
- size="xs"
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+ }
+ onActivation={[Function]}
+ onDeactivation={[Function]}
+ persistentFocus={false}
+ returnFocus={[Function]}
+ shards={Array []}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Recently viewed
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
- close
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`CollapsibleNav renders the default nav 1`] = `
-
-
-
-`;
-
-exports[`CollapsibleNav renders the default nav 2`] = `
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Observability
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dock navigation
+
+
+
+
+
+
+
+
+
+
+
+ close
+
+
+
+
+
+ }
+ onActivation={[Function]}
+ onDeactivation={[Function]}
+ persistentFocus={false}
+ returnFocus={[Function]}
+ shards={Array []}
+ />
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Recently viewed
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
+ data-test-subj="collapsibleNavGroup-recentlyViewed"
+ id="mockId"
+ initialIsOpen={true}
+ onToggle={[Function]}
+ paddingSize="none"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Recently viewed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+ Kibana
+
+
+
+
}
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
+ data-test-subj="collapsibleNavGroup-kibana"
+ id="mockId"
+ initialIsOpen={true}
+ onToggle={[Function]}
+ paddingSize="none"
>
-
-
-
-
-
-
+
+
+
+
- Home
+
+
+
+
+
+
+
+
+
+ Kibana
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
- data-test-subj="collapsibleNavGroup-recentlyViewed"
- id="mockId"
- initialIsOpen={true}
- onToggle={[Function]}
- paddingSize="none"
- >
-
+
+
+
-
+
+
+
+
+
+
+ Observability
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
+ data-test-subj="collapsibleNavGroup-observability"
+ id="mockId"
+ initialIsOpen={true}
+ onToggle={[Function]}
+ paddingSize="none"
>
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Observability
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+ Security
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
+ data-test-subj="collapsibleNavGroup-security"
+ id="mockId"
+ initialIsOpen={true}
+ onToggle={[Function]}
+ paddingSize="none"
>
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Security
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- Recently viewed
+ Management
-
-
-
-
-
-
-
-
-
-
-
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading"
+ data-test-subj="collapsibleNavGroup-management"
+ id="mockId"
+ initialIsOpen={true}
+ onToggle={[Function]}
+ paddingSize="none"
>
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Management
+
+
+
+
+
+
+
+
+
+
+
+
-
- No recently viewed items
-
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
- Dock navigation
-
- ,
- }
- }
- color="subdued"
- data-test-subj="collapsible-nav-lock"
- iconType="lockOpen"
- label="Dock navigation"
- onClick={[Function]}
- size="xs"
+
-
-
-
-
-
-
+
+ canvas
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dock navigation
+
+ ,
+ }
+ }
+ color="subdued"
+ data-test-subj="collapsible-nav-lock"
+ iconType="lockOpen"
+ label="Dock navigation"
+ onClick={[Function]}
+ size="xs"
>
- Dock navigation
-
-
-
-
-
-
-
+
+
+
+
+
+
+ Dock navigation
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
-
-
-
-
-
- close
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ close
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ >
+
+
+
+
+
+
+
+
+
+
@@ -4446,7 +4960,242 @@ exports[`CollapsibleNav renders the default nav 2`] = `
`;
-exports[`CollapsibleNav renders the default nav 3`] = `
+exports[`CollapsibleNav renders the default nav 1`] = `
+
+
+
+`;
+
+exports[`CollapsibleNav renders the default nav 2`] = `
@@ -4682,12 +5431,16 @@ exports[`CollapsibleNav renders the default nav 3`] = `
event="keydown"
handler={[Function]}
/>
+
-
-
-
-
-
+
-
-
-
-
-
-
-
-
- No recently viewed items
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- close
-
-
-
-
-
- }
- onActivation={[Function]}
- onDeactivation={[Function]}
- persistentFocus={false}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Recently viewed
+
+
+
+
+
+
+
+
+
+
+
+
-
- No recently viewed items
-
+
+
+
+
+
+ No recently viewed items
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
-
+
+
-
-
-
- close
-
-
-
-
-
+
+
+
+
+
+
+ close
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
- data-test-subj="collapsibleNavGroup-recentlyViewed"
- id="mockId"
- initialIsOpen={true}
- onToggle={[Function]}
- paddingSize="none"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`CollapsibleNav renders the default nav 3`] = `
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
-
+
+
+
+ Recently viewed
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
+ data-test-subj="collapsibleNavGroup-recentlyViewed"
id="mockId"
+ initialIsOpen={true}
+ onToggle={[Function]}
+ paddingSize="none"
>
-
-
-
+
+
+
+
+
+
+
+
+
-
- Undock navigation
-
- ,
- }
- }
- color="subdued"
- data-test-subj="collapsible-nav-lock"
- iconType="lock"
- label="Undock navigation"
- onClick={[Function]}
- size="xs"
- >
-
+
+ Recently viewed
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
- Undock navigation
-
-
-
-
-
-
+ className="euiText euiText--small"
+ style={
+ Object {
+ "padding": "0 8px 8px",
+ }
+ }
+ >
+
+
+
+ No recently viewed items
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+ Undock navigation
+
+ ,
+ }
+ }
+ color="subdued"
+ data-test-subj="collapsible-nav-lock"
+ iconType="lock"
+ label="Undock navigation"
+ onClick={[Function]}
+ size="xs"
+ >
+
+
+
+
+
+
+ Undock navigation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- close
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ close
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
index ce56b19f82cd0..a1920154d9f71 100644
--- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
+++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
@@ -9016,953 +9016,264 @@ exports[`Header renders 3`] = `
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- close
-
-
-
-
-
+ lockProps={
+ Object {
+ "allowPinchZoom": undefined,
+ "enabled": false,
+ "inert": undefined,
+ "shards": undefined,
+ "sideCar": [Function],
+ }
}
- onActivation={[Function]}
- onDeactivation={[Function]}
+ noFocusGuards={false}
persistentFocus={false}
+ returnFocus={true}
+ sideCar={[Function]}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- close
-
-
-
-
-
+
-
-
-
+
-
-
-
-
-
-
-
-
- Manage cloud deployment
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Manage cloud deployment
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
- }
- className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
- data-test-subj="collapsibleNavGroup-recentlyViewed"
- id="mockId"
- initialIsOpen={true}
- onToggle={[Function]}
- paddingSize="none"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Recently viewed
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
+
+
+
+ Recently viewed
+
+
+
+
+ }
+ className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading"
+ data-test-subj="collapsibleNavGroup-recentlyViewed"
+ id="mockId"
+ initialIsOpen={true}
+ onToggle={[Function]}
+ paddingSize="none"
>
-
-
-
-
+
+
+
+
+
-
-
- kibana
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Recently viewed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
- Undock navigation
-
- ,
- }
- }
- color="subdued"
- data-test-subj="collapsible-nav-lock"
- iconType="lock"
- label="Undock navigation"
- onClick={[Function]}
- size="xs"
+
-
-
-
-
-
-
+
+ kibana
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Undock navigation
+
+ ,
+ }
+ }
+ color="subdued"
+ data-test-subj="collapsible-nav-lock"
+ iconType="lock"
+ label="Undock navigation"
+ onClick={[Function]}
+ size="xs"
>
- Undock navigation
-
-
-
-
-
-
-
+
+
+
+
+
+
+ Undock navigation
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
-
-
-
-
-
- close
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ close
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index 8853d95181994..bd279baa78d98 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -127,6 +127,10 @@ export class DocLinksService {
kibanaSearchSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-search-settings`,
dashboardSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-dashboard-settings`,
},
+ visualize: {
+ guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/visualize.html`,
+ timelionDeprecation: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/timelion.html#timelion-deprecation`,
+ },
},
});
}
@@ -225,5 +229,6 @@ export interface DocLinksStart {
readonly dateMath: string;
};
readonly management: Record;
+ readonly visualize: Record;
};
}
diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
index b17e7d0fec773..fb00ddc38c6dc 100644
--- a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
+++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
@@ -31,7 +31,7 @@ Array [
]
`;
-exports[`ModalService openConfirm() renders a mountpoint confirm message 2`] = `"
"`;
+exports[`ModalService openConfirm() renders a mountpoint confirm message 2`] = `"
"`;
exports[`ModalService openConfirm() renders a string confirm message 1`] = `
Array [
@@ -53,7 +53,7 @@ Array [
]
`;
-exports[`ModalService openConfirm() renders a string confirm message 2`] = `"
"`;
+exports[`ModalService openConfirm() renders a string confirm message 2`] = `"
"`;
exports[`ModalService openConfirm() with a currently active confirm replaces the current confirm with the new one 1`] = `
Array [
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 0e879d16b4637..17626418cbeeb 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -591,6 +591,7 @@ export interface DocLinksStart {
readonly dateMath: string;
};
readonly management: Record;
+ readonly visualize: Record;
};
}
diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts
index 501ab619316c2..26186efc286bf 100644
--- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts
+++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts
@@ -34,6 +34,7 @@ import { ServiceStatus, ServiceStatusLevels } from '../status';
interface MockedElasticSearchServiceSetup {
legacy: {
+ config$: BehaviorSubject;
createClient: jest.Mock;
client: jest.Mocked;
};
@@ -49,6 +50,7 @@ type MockedElasticSearchServiceStart = MockedElasticSearchServiceSetup & {
const createSetupContractMock = () => {
const setupContract: MockedElasticSearchServiceSetup = {
legacy: {
+ config$: new BehaviorSubject({} as ElasticsearchConfig),
createClient: jest.fn(),
client: legacyClientMock.createClusterClient(),
},
@@ -65,6 +67,7 @@ const createStartContractMock = () => {
client: elasticsearchClientMock.createClusterClient(),
createClient: jest.fn(),
legacy: {
+ config$: new BehaviorSubject({} as ElasticsearchConfig),
createClient: jest.fn(),
client: legacyClientMock.createClusterClient(),
},
@@ -99,7 +102,6 @@ const createInternalSetupContractMock = () => {
summary: 'Elasticsearch is available',
}),
legacy: {
- config$: new BehaviorSubject({} as ElasticsearchConfig),
...createSetupContractMock().legacy,
},
};
diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts
index 69bf593dd5862..2cc065aaaaeb1 100644
--- a/src/core/server/elasticsearch/elasticsearch_service.ts
+++ b/src/core/server/elasticsearch/elasticsearch_service.ts
@@ -123,6 +123,7 @@ export class ElasticsearchService
client: this.client!,
createClient,
legacy: {
+ config$: this.config$,
client: this.legacyClient,
createClient: this.createLegacyCustomClient,
},
diff --git a/src/core/server/elasticsearch/status.test.ts b/src/core/server/elasticsearch/status.test.ts
index ef7ca7cd04608..5dfadba4c88b2 100644
--- a/src/core/server/elasticsearch/status.test.ts
+++ b/src/core/server/elasticsearch/status.test.ts
@@ -65,7 +65,7 @@ describe('calculateStatus', () => {
});
});
- it('changes to degraded when isCompatible and warningNodes present', async () => {
+ it('changes to available with a differemnt message when isCompatible and warningNodes present', async () => {
expect(
await calculateStatus$(
of({
@@ -81,7 +81,7 @@ describe('calculateStatus', () => {
.pipe(take(2))
.toPromise()
).toEqual({
- level: ServiceStatusLevels.degraded,
+ level: ServiceStatusLevels.available,
summary: 'Some nodes are a different version',
meta: {
incompatibleNodes: [],
@@ -188,7 +188,7 @@ describe('calculateStatus', () => {
"summary": "Incompatible with Elasticsearch",
},
Object {
- "level": degraded,
+ "level": available,
"meta": Object {
"incompatibleNodes": Array [],
"warningNodes": Array [
diff --git a/src/core/server/elasticsearch/status.ts b/src/core/server/elasticsearch/status.ts
index 1eaa338af1239..1be32d03c60cb 100644
--- a/src/core/server/elasticsearch/status.ts
+++ b/src/core/server/elasticsearch/status.ts
@@ -55,7 +55,7 @@ export const calculateStatus$ = (
};
} else if (warningNodes.length > 0) {
return {
- level: ServiceStatusLevels.degraded,
+ level: ServiceStatusLevels.available,
summary:
// Message should always be present, but this is a safe fallback
message ??
diff --git a/src/core/server/elasticsearch/types.ts b/src/core/server/elasticsearch/types.ts
index 88094af8047e7..55b5549a56a23 100644
--- a/src/core/server/elasticsearch/types.ts
+++ b/src/core/server/elasticsearch/types.ts
@@ -37,9 +37,14 @@ export interface ElasticsearchServiceSetup {
/**
* @deprecated
* Use {@link ElasticsearchServiceStart.legacy} instead.
- *
- * */
+ */
legacy: {
+ /**
+ * Provide direct access to the current elasticsearch configuration.
+ *
+ * @deprecated this will be removed in a later version.
+ */
+ readonly config$: Observable;
/**
* @deprecated
* Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.createClient} instead.
@@ -82,11 +87,7 @@ export interface ElasticsearchServiceSetup {
}
/** @internal */
-export interface InternalElasticsearchServiceSetup {
- // Required for the BWC with the legacy Kibana only.
- readonly legacy: ElasticsearchServiceSetup['legacy'] & {
- readonly config$: Observable;
- };
+export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceSetup {
esNodesCompatibility$: Observable;
status$: Observable>;
}
@@ -132,6 +133,12 @@ export interface ElasticsearchServiceStart {
* Switch to the new elasticsearch client as soon as https://github.com/elastic/kibana/issues/35508 done.
* */
legacy: {
+ /**
+ * Provide direct access to the current elasticsearch configuration.
+ *
+ * @deprecated this will be removed in a later version.
+ */
+ readonly config$: Observable;
/**
* Create application specific Elasticsearch cluster API client with customized config. See {@link ILegacyClusterClient}.
*
diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts
index 007d75a69b955..abe70e003732b 100644
--- a/src/core/server/http/http_server.test.ts
+++ b/src/core/server/http/http_server.test.ts
@@ -799,6 +799,7 @@ test('exposes route details of incoming request to a route handler', async () =>
authRequired: true,
xsrfRequired: false,
tags: [],
+ timeout: {},
},
});
});
@@ -906,6 +907,9 @@ test('exposes route details of incoming request to a route handler (POST + paylo
authRequired: true,
xsrfRequired: true,
tags: [],
+ timeout: {
+ payload: 10000,
+ },
body: {
parse: true, // hapi populates the default
maxBytes: 1024, // hapi populates the default
@@ -993,129 +997,249 @@ describe('body options', () => {
});
describe('timeout options', () => {
- test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a POST', async () => {
- const { registerRouter, server: innerServer } = await server.setup(config);
+ describe('payload timeout', () => {
+ test('POST routes set the payload timeout', async () => {
+ const { registerRouter, server: innerServer } = await server.setup(config);
+
+ const router = new Router('', logger, enhanceWithContext);
+ router.post(
+ {
+ path: '/',
+ validate: false,
+ options: {
+ timeout: {
+ payload: 300000,
+ },
+ },
+ },
+ (context, req, res) => {
+ try {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
+ } catch (err) {
+ return res.internalError({ body: err.message });
+ }
+ }
+ );
+ registerRouter(router);
+ await server.start();
+ await supertest(innerServer.listener)
+ .post('/')
+ .send({ test: 1 })
+ .expect(200, {
+ timeout: {
+ payload: 300000,
+ },
+ });
+ });
- const router = new Router('', logger, enhanceWithContext);
- router.post(
- {
- path: '/',
- validate: false,
- options: { timeout: 300000 },
- },
- (context, req, res) => {
- try {
- return res.ok({ body: { timeout: req.route.options.timeout } });
- } catch (err) {
- return res.internalError({ body: err.message });
+ test('DELETE routes set the payload timeout', async () => {
+ const { registerRouter, server: innerServer } = await server.setup(config);
+
+ const router = new Router('', logger, enhanceWithContext);
+ router.delete(
+ {
+ path: '/',
+ validate: false,
+ options: {
+ timeout: {
+ payload: 300000,
+ },
+ },
+ },
+ (context, req, res) => {
+ try {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
+ } catch (err) {
+ return res.internalError({ body: err.message });
+ }
}
- }
- );
- registerRouter(router);
- await server.start();
- await supertest(innerServer.listener).post('/').send({ test: 1 }).expect(200, {
- timeout: 300000,
+ );
+ registerRouter(router);
+ await server.start();
+ await supertest(innerServer.listener)
+ .delete('/')
+ .expect(200, {
+ timeout: {
+ payload: 300000,
+ },
+ });
});
- });
- test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a GET', async () => {
- const { registerRouter, server: innerServer } = await server.setup(config);
+ test('PUT routes set the payload timeout and automatically adjusts the idle socket timeout', async () => {
+ const { registerRouter, server: innerServer } = await server.setup(config);
+
+ const router = new Router('', logger, enhanceWithContext);
+ router.put(
+ {
+ path: '/',
+ validate: false,
+ options: {
+ timeout: {
+ payload: 300000,
+ },
+ },
+ },
+ (context, req, res) => {
+ try {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
+ } catch (err) {
+ return res.internalError({ body: err.message });
+ }
+ }
+ );
+ registerRouter(router);
+ await server.start();
+ await supertest(innerServer.listener)
+ .put('/')
+ .expect(200, {
+ timeout: {
+ payload: 300000,
+ },
+ });
+ });
- const router = new Router('', logger, enhanceWithContext);
- router.get(
- {
- path: '/',
- validate: false,
- options: { timeout: 300000 },
- },
- (context, req, res) => {
- try {
- return res.ok({ body: { timeout: req.route.options.timeout } });
- } catch (err) {
- return res.internalError({ body: err.message });
+ test('PATCH routes set the payload timeout and automatically adjusts the idle socket timeout', async () => {
+ const { registerRouter, server: innerServer } = await server.setup(config);
+
+ const router = new Router('', logger, enhanceWithContext);
+ router.patch(
+ {
+ path: '/',
+ validate: false,
+ options: {
+ timeout: {
+ payload: 300000,
+ },
+ },
+ },
+ (context, req, res) => {
+ try {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
+ } catch (err) {
+ return res.internalError({ body: err.message });
+ }
}
- }
- );
- registerRouter(router);
- await server.start();
- await supertest(innerServer.listener).get('/').expect(200, {
- timeout: 300000,
+ );
+ registerRouter(router);
+ await server.start();
+ await supertest(innerServer.listener)
+ .patch('/')
+ .expect(200, {
+ timeout: {
+ payload: 300000,
+ },
+ });
});
});
- test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a DELETE', async () => {
- const { registerRouter, server: innerServer } = await server.setup(config);
+ describe('idleSocket timeout', () => {
+ test('uses server socket timeout when not specified in the route', async () => {
+ const { registerRouter, server: innerServer } = await server.setup({
+ ...config,
+ socketTimeout: 11000,
+ });
- const router = new Router('', logger, enhanceWithContext);
- router.delete(
- {
- path: '/',
- validate: false,
- options: { timeout: 300000 },
- },
- (context, req, res) => {
- try {
- return res.ok({ body: { timeout: req.route.options.timeout } });
- } catch (err) {
- return res.internalError({ body: err.message });
+ const router = new Router('', logger, enhanceWithContext);
+ router.get(
+ {
+ path: '/',
+ validate: { body: schema.any() },
+ },
+ (context, req, res) => {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
}
- }
- );
- registerRouter(router);
- await server.start();
- await supertest(innerServer.listener).delete('/').expect(200, {
- timeout: 300000,
+ );
+ registerRouter(router);
+
+ await server.start();
+ await supertest(innerServer.listener)
+ .get('/')
+ .send()
+ .expect(200, {
+ timeout: {
+ idleSocket: 11000,
+ },
+ });
});
- });
- test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a PUT', async () => {
- const { registerRouter, server: innerServer } = await server.setup(config);
+ test('sets the socket timeout when specified in the route', async () => {
+ const { registerRouter, server: innerServer } = await server.setup({
+ ...config,
+ socketTimeout: 11000,
+ });
- const router = new Router('', logger, enhanceWithContext);
- router.put(
- {
- path: '/',
- validate: false,
- options: { timeout: 300000 },
- },
- (context, req, res) => {
- try {
- return res.ok({ body: { timeout: req.route.options.timeout } });
- } catch (err) {
- return res.internalError({ body: err.message });
+ const router = new Router('', logger, enhanceWithContext);
+ router.get(
+ {
+ path: '/',
+ validate: { body: schema.any() },
+ options: { timeout: { idleSocket: 12000 } },
+ },
+ (context, req, res) => {
+ return res.ok({
+ body: {
+ timeout: req.route.options.timeout,
+ },
+ });
}
- }
- );
- registerRouter(router);
- await server.start();
- await supertest(innerServer.listener).put('/').expect(200, {
- timeout: 300000,
+ );
+ registerRouter(router);
+
+ await server.start();
+ await supertest(innerServer.listener)
+ .get('/')
+ .send()
+ .expect(200, {
+ timeout: {
+ idleSocket: 12000,
+ },
+ });
});
});
- test('should accept a socket "timeout" which is 3 minutes in milliseconds, "300000" for a PATCH', async () => {
- const { registerRouter, server: innerServer } = await server.setup(config);
+ test(`idleSocket timeout can be smaller than the payload timeout`, async () => {
+ const { registerRouter } = await server.setup(config);
const router = new Router('', logger, enhanceWithContext);
- router.patch(
+ router.post(
{
path: '/',
- validate: false,
- options: { timeout: 300000 },
+ validate: { body: schema.any() },
+ options: {
+ timeout: {
+ payload: 1000,
+ idleSocket: 10,
+ },
+ },
},
(context, req, res) => {
- try {
- return res.ok({ body: { timeout: req.route.options.timeout } });
- } catch (err) {
- return res.internalError({ body: err.message });
- }
+ return res.ok({ body: { timeout: req.route.options.timeout } });
}
);
+
registerRouter(router);
+
await server.start();
- await supertest(innerServer.listener).patch('/').expect(200, {
- timeout: 300000,
- });
});
});
diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts
index 4b70f58deba99..99ab0ef16c2f9 100644
--- a/src/core/server/http/http_server.ts
+++ b/src/core/server/http/http_server.ts
@@ -163,13 +163,17 @@ export class HttpServer {
const validate = isSafeMethod(route.method) ? undefined : { payload: true };
const { authRequired, tags, body = {}, timeout } = route.options;
const { accepts: allow, maxBytes, output, parse } = body;
- // Hapi does not allow timeouts on payloads to be specified for 'head' or 'get' requests
- const payloadTimeout = isSafeMethod(route.method) || timeout == null ? undefined : timeout;
const kibanaRouteState: KibanaRouteState = {
xsrfRequired: route.options.xsrfRequired ?? !isSafeMethod(route.method),
};
+ // To work around https://github.com/hapijs/hapi/issues/4122 until v20, set the socket
+ // timeout on the route to a fake timeout only when the payload timeout is specified.
+ // Within the onPreAuth lifecycle of the route itself, we'll override the timeout with the
+ // real socket timeout.
+ const fakeSocketTimeout = timeout?.payload ? timeout.payload + 1 : undefined;
+
this.server.route({
handler: route.handler,
method: route.method,
@@ -177,13 +181,29 @@ export class HttpServer {
options: {
auth: this.getAuthOption(authRequired),
app: kibanaRouteState,
+ ext: {
+ onPreAuth: {
+ method: (request, h) => {
+ // At this point, the socket timeout has only been set to work-around the HapiJS bug.
+ // We need to either set the real per-route timeout or use the default idle socket timeout
+ if (timeout?.idleSocket) {
+ request.raw.req.socket.setTimeout(timeout.idleSocket);
+ } else if (fakeSocketTimeout) {
+ // NodeJS uses a socket timeout of `0` to denote "no timeout"
+ request.raw.req.socket.setTimeout(this.config!.socketTimeout ?? 0);
+ }
+
+ return h.continue;
+ },
+ },
+ },
tags: tags ? Array.from(tags) : undefined,
// TODO: This 'validate' section can be removed once the legacy platform is completely removed.
// We are telling Hapi that NP routes can accept any payload, so that it can bypass the default
// validation applied in ./http_tools#getServerOptions
// (All NP routes are already required to specify their own validation in order to access the payload)
validate,
- payload: [allow, maxBytes, output, parse, payloadTimeout].some(
+ payload: [allow, maxBytes, output, parse, timeout?.payload].some(
(v) => typeof v !== 'undefined'
)
? {
@@ -191,15 +211,12 @@ export class HttpServer {
maxBytes,
output,
parse,
- timeout: payloadTimeout,
+ timeout: timeout?.payload,
}
: undefined,
- timeout:
- timeout != null
- ? {
- socket: timeout + 1, // Hapi server requires the socket to be greater than payload settings so we add 1 millisecond
- }
- : undefined,
+ timeout: {
+ socket: fakeSocketTimeout,
+ },
},
});
}
diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts
index 434e22e3cf6f5..e19c348511f1a 100644
--- a/src/core/server/http/integration_tests/router.test.ts
+++ b/src/core/server/http/integration_tests/router.test.ts
@@ -304,126 +304,204 @@ describe('Options', () => {
});
describe('timeout', () => {
- it('should timeout if configured with a small timeout value for a POST', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ const writeBodyCharAtATime = (request: supertest.Test, body: string, interval: number) => {
+ return new Promise((resolve, reject) => {
+ let i = 0;
+ const intervalId = setInterval(() => {
+ if (i < body.length) {
+ request.write(body[i++]);
+ } else {
+ clearInterval(intervalId);
+ request.end((err, res) => {
+ resolve(res);
+ });
+ }
+ }, interval);
+ request.on('error', (err) => {
+ clearInterval(intervalId);
+ reject(err);
+ });
+ });
+ };
- router.post(
- { path: '/a', validate: false, options: { timeout: 1000 } },
- async (context, req, res) => {
- await new Promise((resolve) => setTimeout(resolve, 2000));
- return res.ok({});
- }
- );
- router.post({ path: '/b', validate: false }, (context, req, res) => res.ok({}));
- await server.start();
- expect(supertest(innerServer.listener).post('/a')).rejects.toThrow('socket hang up');
- await supertest(innerServer.listener).post('/b').expect(200, {});
- });
+ describe('payload', () => {
+ it('should timeout if POST payload sending is too slow', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
- it('should timeout if configured with a small timeout value for a PUT', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ router.post(
+ {
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { payload: 100 },
+ },
+ path: '/a',
+ validate: false,
+ },
+ async (context, req, res) => {
+ return res.ok({});
+ }
+ );
+ await server.start();
- router.put(
- { path: '/a', validate: false, options: { timeout: 1000 } },
- async (context, req, res) => {
- await new Promise((resolve) => setTimeout(resolve, 2000));
- return res.ok({});
- }
- );
- router.put({ path: '/b', validate: false }, (context, req, res) => res.ok({}));
- await server.start();
+ // start the request
+ const request = supertest(innerServer.listener)
+ .post('/a')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked');
- expect(supertest(innerServer.listener).put('/a')).rejects.toThrow('socket hang up');
- await supertest(innerServer.listener).put('/b').expect(200, {});
- });
+ const result = writeBodyCharAtATime(request, '{"foo":"bar"}', 10);
- it('should timeout if configured with a small timeout value for a DELETE', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ await expect(result).rejects.toMatchInlineSnapshot(`[Error: Request Timeout]`);
+ });
- router.delete(
- { path: '/a', validate: false, options: { timeout: 1000 } },
- async (context, req, res) => {
- await new Promise((resolve) => setTimeout(resolve, 2000));
- return res.ok({});
- }
- );
- router.delete({ path: '/b', validate: false }, (context, req, res) => res.ok({}));
- await server.start();
- expect(supertest(innerServer.listener).delete('/a')).rejects.toThrow('socket hang up');
- await supertest(innerServer.listener).delete('/b').expect(200, {});
- });
+ it('should not timeout if POST payload sending is quick', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
- it('should timeout if configured with a small timeout value for a GET', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ router.post(
+ {
+ path: '/a',
+ validate: false,
+ options: { body: { accepts: 'application/json' }, timeout: { payload: 10000 } },
+ },
+ async (context, req, res) => res.ok({})
+ );
+ await server.start();
- router.get(
- // Note: There is a bug within Hapi Server where it cannot set the payload timeout for a GET call but it also cannot configure a timeout less than the payload body
- // so the least amount of possible time to configure the timeout is 10 seconds.
- { path: '/a', validate: false, options: { timeout: 100000 } },
- async (context, req, res) => {
- // Cause a wait of 20 seconds to cause the socket hangup
- await new Promise((resolve) => setTimeout(resolve, 200000));
- return res.ok({});
- }
- );
- router.get({ path: '/b', validate: false }, (context, req, res) => res.ok({}));
- await server.start();
+ // start the request
+ const request = supertest(innerServer.listener)
+ .post('/a')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked');
+
+ const result = writeBodyCharAtATime(request, '{}', 10);
- expect(supertest(innerServer.listener).get('/a')).rejects.toThrow('socket hang up');
- await supertest(innerServer.listener).get('/b').expect(200, {});
+ await expect(result).resolves.toHaveProperty('status', 200);
+ });
});
- it('should not timeout if configured with a 5 minute timeout value for a POST', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ describe('idleSocket', () => {
+ it('should timeout if payload sending has too long of an idle period', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
- router.post(
- { path: '/a', validate: false, options: { timeout: 300000 } },
- async (context, req, res) => res.ok({})
- );
- await server.start();
- await supertest(innerServer.listener).post('/a').expect(200, {});
- });
+ router.post(
+ {
+ path: '/a',
+ validate: false,
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 10 },
+ },
+ },
+ async (context, req, res) => {
+ return res.ok({});
+ }
+ );
- it('should not timeout if configured with a 5 minute timeout value for a PUT', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ await server.start();
- router.put(
- { path: '/a', validate: false, options: { timeout: 300000 } },
- async (context, req, res) => res.ok({})
- );
- await server.start();
+ // start the request
+ const request = supertest(innerServer.listener)
+ .post('/a')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked');
- await supertest(innerServer.listener).put('/a').expect(200, {});
- });
+ const result = writeBodyCharAtATime(request, '{}', 20);
- it('should not timeout if configured with a 5 minute timeout value for a DELETE', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ await expect(result).rejects.toThrow('socket hang up');
+ });
- router.delete(
- { path: '/a', validate: false, options: { timeout: 300000 } },
- async (context, req, res) => res.ok({})
- );
- await server.start();
- await supertest(innerServer.listener).delete('/a').expect(200, {});
- });
+ it(`should not timeout if payload sending doesn't have too long of an idle period`, async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
- it('should not timeout if configured with a 5 minute timeout value for a GET', async () => {
- const { server: innerServer, createRouter } = await server.setup(setupDeps);
- const router = createRouter('/');
+ router.post(
+ {
+ path: '/a',
+ validate: false,
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 1000 },
+ },
+ },
+ async (context, req, res) => {
+ return res.ok({});
+ }
+ );
- router.get(
- { path: '/a', validate: false, options: { timeout: 300000 } },
- async (context, req, res) => res.ok({})
- );
- await server.start();
- await supertest(innerServer.listener).get('/a').expect(200, {});
+ await server.start();
+
+ // start the request
+ const request = supertest(innerServer.listener)
+ .post('/a')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked');
+
+ const result = writeBodyCharAtATime(request, '{}', 10);
+
+ await expect(result).resolves.toHaveProperty('status', 200);
+ });
+
+ it('should timeout if servers response is too slow', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
+
+ router.post(
+ {
+ path: '/a',
+ validate: false,
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 1000, payload: 100 },
+ },
+ },
+ async (context, req, res) => {
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ return res.ok({});
+ }
+ );
+
+ await server.start();
+ await expect(supertest(innerServer.listener).post('/a')).rejects.toThrow('socket hang up');
+ });
+
+ it('should not timeout if servers response is quick', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
+
+ router.post(
+ {
+ path: '/a',
+ validate: false,
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 2000, payload: 100 },
+ },
+ },
+ async (context, req, res) => {
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ return res.ok({});
+ }
+ );
+
+ await server.start();
+ await expect(supertest(innerServer.listener).post('/a')).resolves.toHaveProperty(
+ 'status',
+ 200
+ );
+ });
});
});
});
diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts
index 93ffb5aa48259..278bc222b754b 100644
--- a/src/core/server/http/router/request.ts
+++ b/src/core/server/http/router/request.ts
@@ -211,15 +211,21 @@ export class KibanaRequest<
private getRouteInfo(request: Request): KibanaRequestRoute {
const method = request.method as Method;
- const { parse, maxBytes, allow, output } = request.route.settings.payload || {};
- const timeout = request.route.settings.timeout?.socket;
+ const { parse, maxBytes, allow, output, timeout: payloadTimeout } =
+ request.route.settings.payload || {};
+ // net.Socket#timeout isn't documented, yet, and isn't part of the types... https://github.com/nodejs/node/pull/34543
+ // the socket is also undefined when using @hapi/shot, or when a "fake request" is used
+ const socketTimeout = (request.raw.req.socket as any)?.timeout;
const options = ({
authRequired: this.getAuthRequired(request),
// some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8
xsrfRequired: (request.route.settings.app as KibanaRouteState)?.xsrfRequired ?? true,
tags: request.route.settings.tags || [],
- timeout: typeof timeout === 'number' ? timeout - 1 : undefined, // We are forced to have the timeout be 1 millisecond greater than the server and payload so we subtract one here to give the user consist settings
+ timeout: {
+ payload: payloadTimeout,
+ idleSocket: socketTimeout === 0 ? undefined : socketTimeout,
+ },
body: isSafeMethod(method)
? undefined
: {
diff --git a/src/core/server/http/router/route.ts b/src/core/server/http/router/route.ts
index 676c494bec522..ce898a34e6b2c 100644
--- a/src/core/server/http/router/route.ts
+++ b/src/core/server/http/router/route.ts
@@ -146,10 +146,19 @@ export interface RouteConfigOptions {
body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody;
/**
- * Timeouts for processing durations. Response timeout is in milliseconds.
- * Default value: 2 minutes
+ * Defines per-route timeouts.
*/
- timeout?: number;
+ timeout?: {
+ /**
+ * Milliseconds to receive the payload
+ */
+ payload?: Method extends 'get' | 'options' ? undefined : number;
+
+ /**
+ * Milliseconds the socket can be idle before it's closed
+ */
+ idleSocket?: number;
+ };
}
/**
diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts
index 976d92e6fe7fb..0c1e8562a1deb 100644
--- a/src/core/server/legacy/legacy_service.ts
+++ b/src/core/server/legacy/legacy_service.ts
@@ -288,10 +288,7 @@ export class LegacyService implements CoreService {
capabilities: setupDeps.core.capabilities,
context: setupDeps.core.context,
elasticsearch: {
- legacy: {
- client: setupDeps.core.elasticsearch.legacy.client,
- createClient: setupDeps.core.elasticsearch.legacy.createClient,
- },
+ legacy: setupDeps.core.elasticsearch.legacy,
},
http: {
createCookieSessionStorageFactory: setupDeps.core.http.createCookieSessionStorageFactory,
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 21ef66230f698..03545284e14fb 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -716,6 +716,7 @@ export class ElasticsearchConfig {
export interface ElasticsearchServiceSetup {
// @deprecated (undocumented)
legacy: {
+ readonly config$: Observable;
readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
};
@@ -727,6 +728,7 @@ export interface ElasticsearchServiceStart {
readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient;
// @deprecated (undocumented)
legacy: {
+ readonly config$: Observable;
readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
};
@@ -1884,7 +1886,10 @@ export interface RouteConfigOptions {
authRequired?: boolean | 'optional';
body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody;
tags?: readonly string[];
- timeout?: number;
+ timeout?: {
+ payload?: Method extends 'get' | 'options' ? undefined : number;
+ idleSocket?: number;
+ };
xsrfRequired?: Method extends 'get' ? never : boolean;
}
diff --git a/src/dev/build/README.md b/src/dev/build/README.md
index 460ab01794334..f6e11af67da33 100644
--- a/src/dev/build/README.md
+++ b/src/dev/build/README.md
@@ -16,6 +16,16 @@ node scripts/build --release
node scripts/build --skip-node-download --debug --no-oss
```
+# Fixing out of memory issues
+
+Building Kibana and its distributables can take a lot of memory to finish successfully. Builds do make use of child processes, which means you can increase the amount of memory available by specifying `NODE_OPTIONS="--max-old-space-size=VALUE-IN-MEGABYTES"`.
+
+```sh
+
+# Use 4GB instead of the standard 1GB for building
+NODE_OPTIONS="--max-old-space-size=4096" node scripts/build --release
+```
+
# Structure
The majority of this logic is extracted from the grunt build that has existed forever, and is designed to maintain the general structure grunt provides including tasks and config. The [build_distributables.js] file defines which tasks are run.
diff --git a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts
index f1700ef7b578c..a5b9e01714f38 100644
--- a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts
+++ b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts
@@ -17,10 +17,15 @@
* under the License.
*/
+import { readFileSync } from 'fs';
+import Path from 'path';
+
import {
ToolingLog,
ToolingLogCollectingWriter,
createAbsolutePathSerializer,
+ createRecursiveSerializer,
+ REPO_ROOT,
} from '@kbn/dev-utils';
import { Config } from '../../lib';
@@ -37,6 +42,14 @@ log.setWriters([testWriter]);
expect.addSnapshotSerializer(createAbsolutePathSerializer());
+const nodeVersion = readFileSync(Path.resolve(REPO_ROOT, '.node-version'), 'utf8').trim();
+expect.addSnapshotSerializer(
+ createRecursiveSerializer(
+ (s) => typeof s === 'string' && s.includes(nodeVersion),
+ (s) => s.split(nodeVersion).join('')
+ )
+);
+
async function setup() {
const config = await Config.create({
isRelease: true,
@@ -74,8 +87,8 @@ it('runs expected fs operations', async () => {
Object {
"copy": Array [
Array [
- /.node_binaries/10.21.0/node.exe,
- /.node_binaries/10.21.0/win32-x64/node.exe,
+ /.node_binaries//node.exe,
+ /.node_binaries//win32-x64/node.exe,
Object {
"clone": true,
},
@@ -83,22 +96,22 @@ it('runs expected fs operations', async () => {
],
"untar": Array [
Array [
- /.node_binaries/10.21.0/node-v10.21.0-linux-x64.tar.gz,
- /.node_binaries/10.21.0/linux-x64,
+ /.node_binaries//node-v-linux-x64.tar.gz,
+ /.node_binaries//linux-x64,
Object {
"strip": 1,
},
],
Array [
- /.node_binaries/10.21.0/node-v10.21.0-linux-arm64.tar.gz,
- /.node_binaries/10.21.0/linux-arm64,
+ /.node_binaries//node-v-linux-arm64.tar.gz,
+ /.node_binaries//linux-arm64,
Object {
"strip": 1,
},
],
Array [
- /.node_binaries/10.21.0/node-v10.21.0-darwin-x64.tar.gz,
- /.node_binaries/10.21.0/darwin-x64,
+ /.node_binaries//node-v-darwin-x64.tar.gz,
+ /.node_binaries//darwin-x64,
Object {
"strip": 1,
},
diff --git a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts
index 19416963d5edd..1a850890a33fe 100644
--- a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts
+++ b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts
@@ -17,10 +17,15 @@
* under the License.
*/
+import Path from 'path';
+import Fs from 'fs';
+
import {
ToolingLog,
ToolingLogCollectingWriter,
createAnyInstanceSerializer,
+ createRecursiveSerializer,
+ REPO_ROOT,
} from '@kbn/dev-utils';
import { Config, Platform } from '../../lib';
@@ -41,6 +46,14 @@ log.setWriters([testWriter]);
expect.addSnapshotSerializer(createAnyInstanceSerializer(Config));
+const nodeVersion = Fs.readFileSync(Path.resolve(REPO_ROOT, '.node-version'), 'utf8').trim();
+expect.addSnapshotSerializer(
+ createRecursiveSerializer(
+ (s) => typeof s === 'string' && s.includes(nodeVersion),
+ (s) => s.split(nodeVersion).join('')
+ )
+);
+
async function setup(actualShaSums?: Record) {
const config = await Config.create({
isRelease: true,
@@ -87,7 +100,7 @@ it('checks shasums for each downloaded node build', async () => {
[MockFunction] {
"calls": Array [
Array [
- "10.21.0",
+ "",
],
],
"results": Array [
diff --git a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts
index 7a8f7316913be..9c6ca78f7146a 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts
@@ -30,7 +30,7 @@ export async function bundleDockerFiles(config: Config, log: ToolingLog, scope:
`Generating kibana${scope.imageFlavor}${scope.ubiImageFlavor} docker build context bundle`
);
- const dockerFilesDirName = `kibana${scope.imageFlavor}${scope.ubiImageFlavor}-${scope.versionTag}-docker-build-context`;
+ const dockerFilesDirName = `kibana${scope.imageFlavor}${scope.ubiImageFlavor}-${scope.version}-docker-build-context`;
const dockerFilesBuildDir = resolve(scope.dockerBuildDir, dockerFilesDirName);
const dockerFilesOutputDir = config.resolveFromTarget(`${dockerFilesDirName}.tar.gz`);
diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker
index 0913d4ba4e83a..d7f137e965327 100755
--- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker
+++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker
@@ -177,6 +177,8 @@ kibana_vars=(
xpack.ingestManager.fleet.tlsCheckDisabled
xpack.ingestManager.registryUrl
xpack.license_management.enabled
+ xpack.maps.enabled
+ xpack.maps.showMapVisualizationTypes
xpack.ml.enabled
xpack.reporting.capture.browser.autoDownload
xpack.reporting.capture.browser.chromium.disableSandbox
@@ -235,6 +237,7 @@ kibana_vars=(
xpack.security.sessionTimeout
xpack.security.session.idleTimeout
xpack.security.session.lifespan
+ xpack.security.session.cleanupInterval
xpack.security.loginAssistanceMessage
xpack.security.loginHelp
xpack.spaces.enabled
diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts
index 0a26729f3502d..6cf4a7af70840 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/run.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts
@@ -40,16 +40,16 @@ export async function runDockerGenerator(
ubi: boolean = false
) {
// UBI var config
- const baseOSImage = ubi ? 'registry.access.redhat.com/ubi7/ubi-minimal:7.7' : 'centos:7';
- const ubiVersionTag = 'ubi7';
+ const baseOSImage = ubi ? 'registry.access.redhat.com/ubi8/ubi-minimal:latest' : 'centos:8';
+ const ubiVersionTag = 'ubi8';
const ubiImageFlavor = ubi ? `-${ubiVersionTag}` : '';
// General docker var config
const license = build.isOss() ? 'ASL 2.0' : 'Elastic License';
const imageFlavor = build.isOss() ? '-oss' : '';
const imageTag = 'docker.elastic.co/kibana/kibana';
- const versionTag = config.getBuildVersion();
- const artifactTarball = `kibana${imageFlavor}-${versionTag}-linux-x86_64.tar.gz`;
+ const version = config.getBuildVersion();
+ const artifactTarball = `kibana${imageFlavor}-${version}-linux-x86_64.tar.gz`;
const artifactsDir = config.resolveFromTarget('.');
const dockerBuildDate = new Date().toISOString();
// That would produce oss, default and default-ubi7
@@ -59,12 +59,12 @@ export async function runDockerGenerator(
build.isOss() ? `oss` : `default${ubiImageFlavor}`
);
const dockerOutputDir = config.resolveFromTarget(
- `kibana${imageFlavor}${ubiImageFlavor}-${versionTag}-docker.tar.gz`
+ `kibana${imageFlavor}${ubiImageFlavor}-${version}-docker.tar.gz`
);
const scope: TemplateContext = {
artifactTarball,
imageFlavor,
- versionTag,
+ version,
license,
artifactsDir,
imageTag,
@@ -73,6 +73,8 @@ export async function runDockerGenerator(
baseOSImage,
ubiImageFlavor,
dockerBuildDate,
+ ubi,
+ revision: config.getBuildSha(),
};
// Verify if we have the needed kibana target in order
diff --git a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts
index 115d4c6927c30..a7c40db44b87e 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts
@@ -20,7 +20,7 @@
export interface TemplateContext {
artifactTarball: string;
imageFlavor: string;
- versionTag: string;
+ version: string;
license: string;
artifactsDir: string;
imageTag: string;
@@ -30,4 +30,6 @@ export interface TemplateContext {
ubiImageFlavor: string;
dockerBuildDate: string;
usePublicArtifact?: boolean;
+ ubi: boolean;
+ revision: string;
}
diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
new file mode 100644
index 0000000000000..d235bfe9d6fbc
--- /dev/null
+++ b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
@@ -0,0 +1,122 @@
+################################################################################
+# This Dockerfile was generated from the template at:
+# src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
+#
+# Beginning of multi stage Dockerfile
+################################################################################
+
+################################################################################
+# Build stage 0 `builder`:
+# Extract Kibana artifact
+################################################################################
+FROM {{{baseOSImage}}} AS builder
+
+{{#ubi}}
+RUN {{packageManager}} install -y findutils tar gzip
+{{/ubi}}
+
+{{#usePublicArtifact}}
+RUN cd /opt && \
+ curl --retry 8 -s -L -O https://artifacts.elastic.co/downloads/kibana/{{artifactTarball}} && \
+ cd -
+{{/usePublicArtifact}}
+
+{{^usePublicArtifact}}
+COPY {{artifactTarball}} /opt
+{{/usePublicArtifact}}
+
+RUN mkdir /usr/share/kibana
+WORKDIR /usr/share/kibana
+RUN tar --strip-components=1 -zxf /opt/{{artifactTarball}}
+# Ensure that group permissions are the same as user permissions.
+# This will help when relying on GID-0 to run Kibana, rather than UID-1000.
+# OpenShift does this, for example.
+# REF: https://docs.openshift.org/latest/creating_images/guidelines.html
+RUN chmod -R g=u /usr/share/kibana
+RUN find /usr/share/kibana -type d -exec chmod g+s {} \;
+
+################################################################################
+# Build stage 1 (the actual Kibana image):
+#
+# Copy kibana from stage 0
+# Add entrypoint
+################################################################################
+FROM {{{baseOSImage}}}
+EXPOSE 5601
+
+RUN for iter in {1..10}; do \
+ {{packageManager}} update --setopt=tsflags=nodocs -y && \
+ {{packageManager}} install --setopt=tsflags=nodocs -y \
+ fontconfig freetype shadow-utils libnss3.so {{#ubi}}findutils{{/ubi}} && \
+ {{packageManager}} clean all && exit_code=0 && break || exit_code=$? && echo "{{packageManager}} error: retry $iter in 10s" && \
+ sleep 10; \
+ done; \
+ (exit $exit_code)
+
+# Add an init process, check the checksum to make sure it's a match
+RUN curl -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64
+RUN echo "37f2c1f0372a45554f1b89924fbb134fc24c3756efaedf11e07f599494e0eff9 /usr/local/bin/dumb-init" | sha256sum -c -
+RUN chmod +x /usr/local/bin/dumb-init
+
+# Bring in Kibana from the initial stage.
+COPY --from=builder --chown=1000:0 /usr/share/kibana /usr/share/kibana
+WORKDIR /usr/share/kibana
+RUN ln -s /usr/share/kibana /opt/kibana
+
+ENV ELASTIC_CONTAINER true
+ENV PATH=/usr/share/kibana/bin:$PATH
+
+# Set some Kibana configuration defaults.
+COPY --chown=1000:0 config/kibana.yml /usr/share/kibana/config/kibana.yml
+
+# Add the launcher/wrapper script. It knows how to interpret environment
+# variables and translate them to Kibana CLI options.
+COPY --chown=1000:0 bin/kibana-docker /usr/local/bin/
+
+# Ensure gid 0 write permissions for OpenShift.
+RUN chmod g+ws /usr/share/kibana && \
+ find /usr/share/kibana -gid 0 -and -not -perm /g+w -exec chmod g+w {} \;
+
+# Remove the suid bit everywhere to mitigate "Stack Clash"
+RUN find / -xdev -perm -4000 -exec chmod u-s {} +
+
+# Provide a non-root user to run the process.
+RUN groupadd --gid 1000 kibana && \
+ useradd --uid 1000 --gid 1000 \
+ --home-dir /usr/share/kibana --no-create-home \
+ kibana
+USER kibana
+
+LABEL org.label-schema.build-date="{{dockerBuildDate}}" \
+ org.label-schema.license="{{license}}" \
+ org.label-schema.name="Kibana" \
+ org.label-schema.schema-version="1.0" \
+ org.label-schema.url="https://www.elastic.co/products/kibana" \
+ org.label-schema.usage="https://www.elastic.co/guide/en/kibana/reference/index.html" \
+ org.label-schema.vcs-ref="{{revision}}" \
+ org.label-schema.vcs-url="https://github.com/elastic/kibana" \
+ org.label-schema.vendor="Elastic" \
+ org.label-schema.version="{{version}}" \
+ org.opencontainers.image.created="{{dockerBuildDate}}" \
+ org.opencontainers.image.documentation="https://www.elastic.co/guide/en/kibana/reference/index.html" \
+ org.opencontainers.image.licenses="{{license}}" \
+ org.opencontainers.image.revision="{{revision}}" \
+ org.opencontainers.image.source="https://github.com/elastic/kibana" \
+ org.opencontainers.image.title="Kibana" \
+ org.opencontainers.image.url="https://www.elastic.co/products/kibana" \
+ org.opencontainers.image.vendor="Elastic" \
+ org.opencontainers.image.version="{{version}}"
+
+{{#ubi}}
+LABEL name="Kibana" \
+ maintainer="infra@elastic.co" \
+ vendor="Elastic" \
+ version="{{version}}" \
+ release="1" \
+ summary="Kibana" \
+ description="Your window into the Elastic Stack."
+{{/ubi}}
+
+ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
+
+CMD ["/usr/local/bin/kibana-docker"]
diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts
index ff6fcf7548d9d..699bba758e1c9 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts
@@ -24,7 +24,7 @@ import { TemplateContext } from '../template_context';
function generator({
imageTag,
imageFlavor,
- versionTag,
+ version,
dockerOutputDir,
baseOSImage,
ubiImageFlavor,
@@ -39,9 +39,9 @@ function generator({
docker pull ${baseOSImage}
echo "Building: kibana${imageFlavor}${ubiImageFlavor}-docker"; \\
- docker build -t ${imageTag}${imageFlavor}${ubiImageFlavor}:${versionTag} -f Dockerfile . || exit 1;
+ docker build -t ${imageTag}${imageFlavor}${ubiImageFlavor}:${version} -f Dockerfile . || exit 1;
- docker save ${imageTag}${imageFlavor}${ubiImageFlavor}:${versionTag} | gzip -c > ${dockerOutputDir}
+ docker save ${imageTag}${imageFlavor}${ubiImageFlavor}:${version} | gzip -c > ${dockerOutputDir}
exit 0
`);
diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts
index ea2f881768c8f..9733021319aee 100755
--- a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts
@@ -17,118 +17,18 @@
* under the License.
*/
-import dedent from 'dedent';
+import { readFileSync } from 'fs';
+import { resolve } from 'path';
+import Mustache from 'mustache';
import { TemplateContext } from '../template_context';
-function generator({
- artifactTarball,
- versionTag,
- license,
- usePublicArtifact,
- baseOSImage,
- ubiImageFlavor,
- dockerBuildDate,
-}: TemplateContext) {
- const copyArtifactTarballInsideDockerOptFolder = () => {
- if (usePublicArtifact) {
- return `RUN cd /opt && curl --retry 8 -s -L -O https://artifacts.elastic.co/downloads/kibana/${artifactTarball} && cd -`;
- }
-
- return `COPY ${artifactTarball} /opt`;
- };
-
- const packageManager = () => {
- if (ubiImageFlavor) {
- return 'microdnf';
- }
-
- return 'yum';
- };
-
- return dedent(`
- #
- # ** THIS IS AN AUTO-GENERATED FILE **
- #
-
- ################################################################################
- # Build stage 0
- # Extract Kibana and make various file manipulations.
- ################################################################################
- FROM ${baseOSImage} AS prep_files
- # Add tar and gzip
- RUN ${packageManager()} update -y && ${packageManager()} install -y tar gzip && ${packageManager()} clean all
- ${copyArtifactTarballInsideDockerOptFolder()}
- RUN mkdir /usr/share/kibana
- WORKDIR /usr/share/kibana
- RUN tar --strip-components=1 -zxf /opt/${artifactTarball}
- # Ensure that group permissions are the same as user permissions.
- # This will help when relying on GID-0 to run Kibana, rather than UID-1000.
- # OpenShift does this, for example.
- # REF: https://docs.openshift.org/latest/creating_images/guidelines.html
- RUN chmod -R g=u /usr/share/kibana
- RUN find /usr/share/kibana -type d -exec chmod g+s {} \\;
-
- ################################################################################
- # Build stage 1
- # Copy prepared files from the previous stage and complete the image.
- ################################################################################
- FROM ${baseOSImage}
- EXPOSE 5601
-
- # Add Reporting dependencies.
- RUN ${packageManager()} update -y && ${packageManager()} install -y fontconfig freetype shadow-utils && ${packageManager()} clean all
-
- # Add an init process, check the checksum to make sure it's a match
- RUN curl -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64
- RUN echo "37f2c1f0372a45554f1b89924fbb134fc24c3756efaedf11e07f599494e0eff9 /usr/local/bin/dumb-init" | sha256sum -c -
- RUN chmod +x /usr/local/bin/dumb-init
-
-
- # Bring in Kibana from the initial stage.
- COPY --from=prep_files --chown=1000:0 /usr/share/kibana /usr/share/kibana
- WORKDIR /usr/share/kibana
- RUN ln -s /usr/share/kibana /opt/kibana
-
- ENV ELASTIC_CONTAINER true
- ENV PATH=/usr/share/kibana/bin:$PATH
-
- # Set some Kibana configuration defaults.
- COPY --chown=1000:0 config/kibana.yml /usr/share/kibana/config/kibana.yml
-
- # Add the launcher/wrapper script. It knows how to interpret environment
- # variables and translate them to Kibana CLI options.
- COPY --chown=1000:0 bin/kibana-docker /usr/local/bin/
-
- # Ensure gid 0 write permissions for OpenShift.
- RUN chmod g+ws /usr/share/kibana && \\
- find /usr/share/kibana -gid 0 -and -not -perm /g+w -exec chmod g+w {} \\;
-
- # Remove the suid bit everywhere to mitigate "Stack Clash"
- RUN find / -xdev -perm -4000 -exec chmod u-s {} +
-
- # Provide a non-root user to run the process.
- RUN groupadd --gid 1000 kibana && \\
- useradd --uid 1000 --gid 1000 \\
- --home-dir /usr/share/kibana --no-create-home \\
- kibana
- USER kibana
-
- LABEL org.label-schema.schema-version="1.0" \\
- org.label-schema.vendor="Elastic" \\
- org.label-schema.name="kibana" \\
- org.label-schema.version="${versionTag}" \\
- org.label-schema.url="https://www.elastic.co/products/kibana" \\
- org.label-schema.vcs-url="https://github.com/elastic/kibana" \\
- org.label-schema.license="${license}" \\
- org.label-schema.usage="https://www.elastic.co/guide/en/kibana/index.html" \\
- org.label-schema.build-date="${dockerBuildDate}" \\
- license="${license}"
-
- ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
-
- CMD ["/usr/local/bin/kibana-docker"]
- `);
+function generator(options: TemplateContext) {
+ const template = readFileSync(resolve(__dirname, './Dockerfile'));
+ return Mustache.render(template.toString(), {
+ packageManager: options.ubiImageFlavor ? 'microdnf' : 'yum',
+ ...options,
+ });
}
export const dockerfileTemplate = {
diff --git a/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana b/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana
index 449fc4e75fce8..c13676ef031b0 100755
--- a/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana
+++ b/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana
@@ -15,6 +15,24 @@
# Description: Kibana
### END INIT INFO
+#
+# Source function libraries if present.
+# (It improves integration with systemd)
+#
+# Red Hat
+if [ -f /etc/rc.d/init.d/functions ]; then
+ . /etc/rc.d/init.d/functions
+
+# Debian
+elif [ -f /lib/lsb/init-functions ]; then
+ . /lib/lsb/init-functions
+
+# SUSE
+elif [ -f /etc/rc.status ]; then
+ . /etc/rc.status
+ rc_reset
+fi
+
name=kibana
program=/usr/share/kibana/bin/kibana
pidfile="/var/run/kibana/$name.pid"
diff --git a/src/dev/i18n/index.ts b/src/dev/i18n/index.ts
index cfc03f1c08b3c..68e6ab1646092 100644
--- a/src/dev/i18n/index.ts
+++ b/src/dev/i18n/index.ts
@@ -21,7 +21,6 @@
export { extractMessagesFromPathToMap } from './extract_default_translations';
// @ts-ignore
export { matchEntriesWithExctractors } from './extract_default_translations';
-// @ts-ignore
export { arrayify, writeFileAsync, readFileAsync, normalizePath, ErrorReporter } from './utils';
export { serializeToJson, serializeToJson5 } from './serializers';
export {
diff --git a/src/dev/i18n/integrate_locale_files.test.ts b/src/dev/i18n/integrate_locale_files.test.ts
index 3bd3dc61c044f..24c24682b2874 100644
--- a/src/dev/i18n/integrate_locale_files.test.ts
+++ b/src/dev/i18n/integrate_locale_files.test.ts
@@ -21,7 +21,6 @@ import { mockMakeDirAsync, mockWriteFileAsync } from './integrate_locale_files.t
import path from 'path';
import { integrateLocaleFiles, verifyMessages } from './integrate_locale_files';
-// @ts-expect-error
import { normalizePath } from './utils';
const localePath = path.resolve(__dirname, '__fixtures__', 'integrate_locale_files', 'fr.json');
diff --git a/src/dev/i18n/integrate_locale_files.ts b/src/dev/i18n/integrate_locale_files.ts
index f9cd6dd1971c7..ed4f7db4376bb 100644
--- a/src/dev/i18n/integrate_locale_files.ts
+++ b/src/dev/i18n/integrate_locale_files.ts
@@ -32,7 +32,6 @@ import {
readFileAsync,
writeFileAsync,
verifyICUMessage,
- // @ts-expect-error
} from './utils';
import { I18nConfig } from './config';
@@ -112,7 +111,7 @@ export function verifyMessages(
if (defaultMessage) {
try {
const message = localizedMessagesMap.get(messageId)!;
- verifyICUMessage(message);
+ verifyICUMessage(typeof message === 'string' ? message : message?.text);
} catch (err) {
if (options.ignoreMalformed) {
localizedMessagesMap.delete(messageId);
diff --git a/src/dev/i18n/__snapshots__/utils.test.js.snap b/src/dev/i18n/utils/__snapshots__/utils.test.js.snap
similarity index 95%
rename from src/dev/i18n/__snapshots__/utils.test.js.snap
rename to src/dev/i18n/utils/__snapshots__/utils.test.js.snap
index b4e15304cca96..147ef5987c1dc 100644
--- a/src/dev/i18n/__snapshots__/utils.test.js.snap
+++ b/src/dev/i18n/utils/__snapshots__/utils.test.js.snap
@@ -9,7 +9,7 @@ exports[`i18n utils should create verbose parser error message 1`] = `
"
`;
-exports[`i18n utils should normalizePath 1`] = `"src/dev/i18n"`;
+exports[`i18n utils should normalizePath 1`] = `"src/dev/i18n/utils"`;
exports[`i18n utils should not escape linebreaks 1`] = `
"Text
diff --git a/src/dev/i18n/utils/index.ts b/src/dev/i18n/utils/index.ts
new file mode 100644
index 0000000000000..3f81ab2ca66c3
--- /dev/null
+++ b/src/dev/i18n/utils/index.ts
@@ -0,0 +1,48 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export {
+ // constants
+ readFileAsync,
+ writeFileAsync,
+ makeDirAsync,
+ accessAsync,
+ globAsync,
+ // functions
+ normalizePath,
+ difference,
+ isPropertyWithKey,
+ isI18nTranslateFunction,
+ node,
+ formatJSString,
+ formatHTMLString,
+ traverseNodes,
+ createParserErrorMessage,
+ checkValuesProperty,
+ extractValueReferencesFromMessage,
+ extractMessageIdFromNode,
+ extractMessageValueFromNode,
+ extractDescriptionValueFromNode,
+ extractValuesKeysFromNode,
+ arrayify,
+ // classes
+ ErrorReporter, // @ts-ignore
+} from './utils';
+
+export { verifyICUMessage } from './verify_icu_message';
diff --git a/src/dev/i18n/utils/intl_types.ts b/src/dev/i18n/utils/intl_types.ts
new file mode 100644
index 0000000000000..adbceaea66540
--- /dev/null
+++ b/src/dev/i18n/utils/intl_types.ts
@@ -0,0 +1,41 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export interface OptionalFormatPatternNode {
+ type: 'optionalFormatPattern';
+ selector: string;
+ value: any;
+}
+
+export interface LinePosition {
+ offset: number;
+ line: number;
+ column: number;
+}
+
+export interface LocationNode {
+ start: LinePosition;
+ end: LinePosition;
+}
+
+export interface SelectFormatNode {
+ type: 'selectFormat';
+ options: OptionalFormatPatternNode[];
+ location: LocationNode;
+}
diff --git a/src/dev/i18n/utils.js b/src/dev/i18n/utils/utils.js
similarity index 95%
rename from src/dev/i18n/utils.js
rename to src/dev/i18n/utils/utils.js
index 11a002fdbf4a8..1d1c3118e0852 100644
--- a/src/dev/i18n/utils.js
+++ b/src/dev/i18n/utils/utils.js
@@ -208,28 +208,6 @@ export function checkValuesProperty(prefixedValuesKeys, defaultMessage, messageI
}
}
-/**
- * Verifies valid ICU message.
- * @param message ICU message.
- * @param messageId ICU message id
- * @returns {undefined}
- */
-export function verifyICUMessage(message) {
- try {
- parser.parse(message);
- } catch (error) {
- if (error.name === 'SyntaxError') {
- const errorWithContext = createParserErrorMessage(message, {
- loc: {
- line: error.location.start.line,
- column: error.location.start.column - 1,
- },
- message: error.message,
- });
- throw errorWithContext;
- }
- }
-}
/**
* Extracts value references from the ICU message.
* @param message ICU message.
diff --git a/src/dev/i18n/utils.test.js b/src/dev/i18n/utils/utils.test.js
similarity index 100%
rename from src/dev/i18n/utils.test.js
rename to src/dev/i18n/utils/utils.test.js
diff --git a/src/dev/i18n/utils/verify_icu_message.test.ts b/src/dev/i18n/utils/verify_icu_message.test.ts
new file mode 100644
index 0000000000000..0a4510352c429
--- /dev/null
+++ b/src/dev/i18n/utils/verify_icu_message.test.ts
@@ -0,0 +1,91 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { verifyICUMessage, checkEnglishOnly } from './verify_icu_message';
+
+describe('verifyICUMessage', () => {
+ it('passes on plain text', () => {
+ const message = 'plain text here';
+ expect(() => verifyICUMessage(message)).not.toThrowError();
+ });
+
+ it('passes on empty string', () => {
+ const message = '';
+ expect(() => verifyICUMessage(message)).not.toThrowError();
+ });
+
+ it('passes on variable icu-syntax', () => {
+ const message = 'Your regular {foobar}';
+ expect(() => verifyICUMessage(message)).not.toThrowError();
+ });
+
+ it('passes on correct plural icu-syntax', () => {
+ const message = `You have {itemCount, plural,
+ =0 {no items}
+ one {1 item}
+ other {{itemCount} items}
+ }.`;
+
+ expect(() => verifyICUMessage(message)).not.toThrowError();
+ });
+
+ it('throws on malformed string', () => {
+ const message =
+ 'CDATA[extended_bounds設定を使用すると、強制的にヒストグラムアグリゲーションを実行し、特定の最小値に対してバケットの作成を開始し、最大値までバケットを作成し続けます。 ]]>\n\t\t\tKibana-SW - String "data.search.aggs.buckets.dateHistogram.extendedBounds.help" in Json.Root "messages\\strings" ';
+
+ expect(() => verifyICUMessage(message)).toThrowError();
+ });
+
+ it('throws on missing curly brackets', () => {
+ const message = `A missing {curly`;
+
+ expect(() => verifyICUMessage(message)).toThrowError();
+ });
+
+ it('throws on incorrect plural icu-syntax', () => {
+ // Notice that small/Medium/Large constants are swapped with the translation strings.
+ const message =
+ '{textScale, select, small {小さい} 中くらい {Medium} 大きい {Large} その他の {{textScale}} }';
+
+ expect(() => verifyICUMessage(message)).toThrowError();
+ });
+});
+
+describe('checkEnglishOnly', () => {
+ it('returns true on english only message', () => {
+ const result = checkEnglishOnly('english');
+
+ expect(result).toEqual(true);
+ });
+ it('returns true on empty message', () => {
+ const result = checkEnglishOnly('');
+
+ expect(result).toEqual(true);
+ });
+ it('returns false on message containing numbers', () => {
+ const result = checkEnglishOnly('english 123');
+
+ expect(result).toEqual(false);
+ });
+ it('returns false on message containing non-english alphabets', () => {
+ const result = checkEnglishOnly('i am 大きい');
+
+ expect(result).toEqual(false);
+ });
+});
diff --git a/src/dev/i18n/utils/verify_icu_message.ts b/src/dev/i18n/utils/verify_icu_message.ts
new file mode 100644
index 0000000000000..683d91c5c4939
--- /dev/null
+++ b/src/dev/i18n/utils/verify_icu_message.ts
@@ -0,0 +1,74 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// @ts-ignore
+import parser from 'intl-messageformat-parser';
+// @ts-ignore
+import { createParserErrorMessage, traverseNodes } from './utils';
+import { SelectFormatNode } from './intl_types';
+
+export function checkEnglishOnly(message: string) {
+ return /^[a-z]*$/i.test(message);
+}
+
+export function verifySelectFormatNode(node: SelectFormatNode) {
+ if (node.type !== 'selectFormat') {
+ throw new parser.SyntaxError(
+ 'Unable to verify select format icu-syntax',
+ 'selectFormat',
+ node.type,
+ node.location
+ );
+ }
+
+ for (const option of node.options) {
+ if (option.type === 'optionalFormatPattern') {
+ if (!checkEnglishOnly(option.selector)) {
+ throw new parser.SyntaxError(
+ 'selectFormat Selector must be in english',
+ 'English only selector',
+ option.selector,
+ node.location
+ );
+ }
+ }
+ }
+}
+
+export function verifyICUMessage(message: string) {
+ try {
+ const results = parser.parse(message);
+ for (const node of results.elements) {
+ if (node.type === 'argumentElement' && node.format?.type === 'selectFormat') {
+ verifySelectFormatNode(node.format);
+ }
+ }
+ } catch (error) {
+ if (error.name === 'SyntaxError') {
+ const errorWithContext = createParserErrorMessage(message, {
+ loc: {
+ line: error.location.start.line,
+ column: error.location.start.column - 1,
+ },
+ message: error.message,
+ });
+ throw errorWithContext;
+ }
+ }
+}
diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js
index 74e1ec5e2b4ed..d46b955f6668d 100644
--- a/src/dev/jest/config.js
+++ b/src/dev/jest/config.js
@@ -78,6 +78,7 @@ export default {
setupFilesAfterEnv: [
'/src/dev/jest/setup/mocks.js',
'/src/dev/jest/setup/react_testing_library.js',
+ '/src/dev/jest/setup/default_timeout.js',
],
coverageDirectory: '/target/kibana-coverage/jest',
coverageReporters: !!process.env.CODE_COVERAGE ? ['json'] : ['html', 'text'],
diff --git a/src/dev/jest/setup/default_timeout.js b/src/dev/jest/setup/default_timeout.js
new file mode 100644
index 0000000000000..eea38e745b960
--- /dev/null
+++ b/src/dev/jest/setup/default_timeout.js
@@ -0,0 +1,25 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* eslint-env jest */
+
+/**
+ * Set the default timeout for the unit tests to 30 seconds, temporarily
+ */
+jest.setTimeout(30 * 1000);
diff --git a/src/dev/run_i18n_check.ts b/src/dev/run_i18n_check.ts
index 70eeedac2b8b6..17f3fd5d1c734 100644
--- a/src/dev/run_i18n_check.ts
+++ b/src/dev/run_i18n_check.ts
@@ -30,7 +30,8 @@ import {
mergeConfigs,
} from './i18n/tasks';
-const skipNoTranslations = ({ config }: { config: I18nConfig }) => !config.translations.length;
+const skipOnNoTranslations = ({ config }: { config: I18nConfig }) =>
+ !config.translations.length && 'No translations found.';
run(
async ({
@@ -40,6 +41,7 @@ run(
'ignore-missing': ignoreMissing,
'ignore-unused': ignoreUnused,
'include-config': includeConfig,
+ 'ignore-untracked': ignoreUntracked,
fix = false,
path,
},
@@ -50,12 +52,13 @@ run(
(ignoreIncompatible !== undefined ||
ignoreUnused !== undefined ||
ignoreMalformed !== undefined ||
- ignoreMissing !== undefined)
+ ignoreMissing !== undefined ||
+ ignoreUntracked !== undefined)
) {
throw createFailError(
`${chalk.white.bgRed(
' I18N ERROR '
- )} none of the --ignore-incompatible, --ignore-malformed, --ignore-unused or --ignore-missing is allowed when --fix is set.`
+ )} none of the --ignore-incompatible, --ignore-malformed, --ignore-unused or --ignore-missing, --ignore-untracked is allowed when --fix is set.`
);
}
@@ -83,19 +86,20 @@ run(
},
{
title: 'Checking For Untracked Messages based on .i18nrc.json',
- skip: skipNoTranslations,
+ enabled: (_) => !ignoreUntracked,
+ skip: skipOnNoTranslations,
task: ({ config }) =>
new Listr(extractUntrackedMessages(srcPaths), { exitOnError: true }),
},
{
title: 'Validating Default Messages',
- skip: skipNoTranslations,
+ skip: skipOnNoTranslations,
task: ({ config }) =>
new Listr(extractDefaultMessages(config, srcPaths), { exitOnError: true }),
},
{
title: 'Compatibility Checks',
- skip: skipNoTranslations,
+ skip: skipOnNoTranslations,
task: ({ config }) =>
new Listr(
checkCompatibility(
diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
index 35380ada51a0a..5ef1149146afe 100644
--- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
+++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
@@ -26,7 +26,7 @@ import {
getAggTypes,
AggConfigs,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../src/plugins/data/public/search/aggs';
+} from '../../../../../src/plugins/data/common/search/aggs';
import { ComponentRegistry } from '../../../../../src/plugins/advanced_settings/public/';
import { UI_SETTINGS } from '../../../../../src/plugins/data/public/';
import {
@@ -184,12 +184,13 @@ const mockAggTypesRegistry = () => {
const registry = new AggTypesRegistry();
const registrySetup = registry.setup();
const aggTypes = getAggTypes({
- uiSettings: mockCoreSetup.uiSettings,
- query: querySetup,
- getInternalStartServices: () => ({
- fieldFormats: getFieldFormatsRegistry(mockCoreStart),
- notifications: mockCoreStart.notifications,
+ calculateBounds: sinon.fake(),
+ getConfig: sinon.fake(),
+ getFieldFormatsStart: () => ({
+ deserialize: sinon.fake(),
+ getDefaultInstance: sinon.fake(),
}),
+ isDefaultTimezone: () => true,
});
aggTypes.buckets.forEach((type) => registrySetup.registerBucket(type));
aggTypes.metrics.forEach((type) => registrySetup.registerMetric(type));
@@ -240,7 +241,6 @@ export const npSetup = {
query: querySetup,
search: {
aggs: {
- calculateAutoTimeExpression: sinon.fake(),
types: aggTypesRegistry.setup(),
},
__LEGACY: {
diff --git a/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx b/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx
index 72992c190e8ae..5b33b0e0ea120 100644
--- a/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx
+++ b/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx
@@ -25,7 +25,6 @@ import { FieldSetting } from '../../types';
import { UiSettingsType, StringValidation } from '../../../../../../core/public';
import { notificationServiceMock, docLinksServiceMock } from '../../../../../../core/public/mocks';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { Field, getEditableValue } from './field';
diff --git a/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx b/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx
index 0e942665b23a9..e42432d0bc319 100644
--- a/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx
+++ b/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx
@@ -21,7 +21,6 @@ import React from 'react';
import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers';
import { UiSettingsType } from '../../../../../../core/public';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { notificationServiceMock } from '../../../../../../core/public/mocks';
diff --git a/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx b/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx
index 0e3fbb3cf97fa..01f54cce60319 100644
--- a/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx
+++ b/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx
@@ -20,7 +20,6 @@
import React from 'react';
import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { Query } from '@elastic/eui';
diff --git a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap
index 698c124d2d805..201c6e83a4a44 100644
--- a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap
+++ b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap
@@ -671,35 +671,54 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = `
iconType="plusInCircle"
size="s"
>
-
-
-
-
-
-
- Create new
-
-
-
+
+
+
+
+
+ Create new
+
+
+
+
+
diff --git a/src/plugins/dashboard/public/application/actions/index.ts b/src/plugins/dashboard/public/application/actions/index.ts
index d7a84fb79f6af..be183976c676f 100644
--- a/src/plugins/dashboard/public/application/actions/index.ts
+++ b/src/plugins/dashboard/public/application/actions/index.ts
@@ -32,3 +32,8 @@ export {
ClonePanelActionContext,
ACTION_CLONE_PANEL,
} from './clone_panel_action';
+export {
+ UnlinkFromLibraryActionContext,
+ ACTION_UNLINK_FROM_LIBRARY,
+ UnlinkFromLibraryAction,
+} from './unlink_from_library_action';
diff --git a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx
new file mode 100644
index 0000000000000..681a6a734a532
--- /dev/null
+++ b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx
@@ -0,0 +1,160 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { isErrorEmbeddable, IContainer, ReferenceOrValueEmbeddable } from '../../embeddable_plugin';
+import { DashboardContainer } from '../embeddable';
+import { getSampleDashboardInput } from '../test_helpers';
+import {
+ CONTACT_CARD_EMBEDDABLE,
+ ContactCardEmbeddableFactory,
+ ContactCardEmbeddable,
+ ContactCardEmbeddableInput,
+ ContactCardEmbeddableOutput,
+} from '../../embeddable_plugin_test_samples';
+import { coreMock } from '../../../../../core/public/mocks';
+import { CoreStart } from 'kibana/public';
+import { UnlinkFromLibraryAction } from '.';
+import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
+import { ViewMode } from '../../../../embeddable/public';
+
+const { setup, doStart } = embeddablePluginMock.createInstance();
+setup.registerEmbeddableFactory(
+ CONTACT_CARD_EMBEDDABLE,
+ new ContactCardEmbeddableFactory((() => null) as any, {} as any)
+);
+const start = doStart();
+
+let container: DashboardContainer;
+let embeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable;
+let coreStart: CoreStart;
+beforeEach(async () => {
+ coreStart = coreMock.createStart();
+
+ const containerOptions = {
+ ExitFullScreenButton: () => null,
+ SavedObjectFinder: () => null,
+ application: {} as any,
+ embeddable: start,
+ inspector: {} as any,
+ notifications: {} as any,
+ overlays: coreStart.overlays,
+ savedObjectMetaData: {} as any,
+ uiActions: {} as any,
+ };
+
+ container = new DashboardContainer(getSampleDashboardInput(), containerOptions);
+
+ const contactCardEmbeddable = await container.addNewEmbeddable<
+ ContactCardEmbeddableInput,
+ ContactCardEmbeddableOutput,
+ ContactCardEmbeddable
+ >(CONTACT_CARD_EMBEDDABLE, {
+ firstName: 'Kibanana',
+ });
+
+ if (isErrorEmbeddable(contactCardEmbeddable)) {
+ throw new Error('Failed to create embeddable');
+ }
+ embeddable = embeddablePluginMock.mockRefOrValEmbeddable<
+ ContactCardEmbeddable,
+ ContactCardEmbeddableInput
+ >(contactCardEmbeddable, {
+ mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: contactCardEmbeddable.id },
+ mockedByValueInput: { firstName: 'Kibanana', id: contactCardEmbeddable.id },
+ });
+ embeddable.updateInput({ viewMode: ViewMode.EDIT });
+});
+
+test('Unlink is compatible when embeddable on dashboard has reference type input', async () => {
+ const action = new UnlinkFromLibraryAction();
+ embeddable.updateInput(await embeddable.getInputAsRefType());
+ expect(await action.isCompatible({ embeddable })).toBe(true);
+});
+
+test('Unlink is not compatible when embeddable input is by value', async () => {
+ const action = new UnlinkFromLibraryAction();
+ embeddable.updateInput(await embeddable.getInputAsValueType());
+ expect(await action.isCompatible({ embeddable })).toBe(false);
+});
+
+test('Unlink is not compatible when view mode is set to view', async () => {
+ const action = new UnlinkFromLibraryAction();
+ embeddable.updateInput(await embeddable.getInputAsRefType());
+ embeddable.updateInput({ viewMode: ViewMode.VIEW });
+ expect(await action.isCompatible({ embeddable })).toBe(false);
+});
+
+test('Unlink is not compatible when embeddable is not in a dashboard container', async () => {
+ let orphanContactCard = await container.addNewEmbeddable<
+ ContactCardEmbeddableInput,
+ ContactCardEmbeddableOutput,
+ ContactCardEmbeddable
+ >(CONTACT_CARD_EMBEDDABLE, {
+ firstName: 'Orphan',
+ });
+ orphanContactCard = embeddablePluginMock.mockRefOrValEmbeddable<
+ ContactCardEmbeddable,
+ ContactCardEmbeddableInput
+ >(orphanContactCard, {
+ mockedByReferenceInput: { savedObjectId: 'test', id: orphanContactCard.id },
+ mockedByValueInput: { firstName: 'Kibanana', id: orphanContactCard.id },
+ });
+ const action = new UnlinkFromLibraryAction();
+ expect(await action.isCompatible({ embeddable: orphanContactCard })).toBe(false);
+});
+
+test('Unlink replaces embeddableId but retains panel count', async () => {
+ const dashboard = embeddable.getRoot() as IContainer;
+ const originalPanelCount = Object.keys(dashboard.getInput().panels).length;
+ const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels));
+ const action = new UnlinkFromLibraryAction();
+ await action.execute({ embeddable });
+ expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount);
+
+ const newPanelId = Object.keys(container.getInput().panels).find(
+ (key) => !originalPanelKeySet.has(key)
+ );
+ expect(newPanelId).toBeDefined();
+ const newPanel = container.getInput().panels[newPanelId!];
+ expect(newPanel.type).toEqual(embeddable.type);
+});
+
+test('Unlink unwraps all attributes from savedObject', async () => {
+ const complicatedAttributes = {
+ attribute1: 'The best attribute',
+ attribute2: 22,
+ attribute3: ['array', 'of', 'strings'],
+ attribute4: { nestedattribute: 'hello from the nest' },
+ };
+
+ embeddable = embeddablePluginMock.mockRefOrValEmbeddable(embeddable, {
+ mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: embeddable.id },
+ mockedByValueInput: { attributes: complicatedAttributes, id: embeddable.id },
+ });
+ const dashboard = embeddable.getRoot() as IContainer;
+ const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels));
+ const action = new UnlinkFromLibraryAction();
+ await action.execute({ embeddable });
+ const newPanelId = Object.keys(container.getInput().panels).find(
+ (key) => !originalPanelKeySet.has(key)
+ );
+ expect(newPanelId).toBeDefined();
+ const newPanel = container.getInput().panels[newPanelId!];
+ expect(newPanel.type).toEqual(embeddable.type);
+ expect(newPanel.explicitInput.attributes).toEqual(complicatedAttributes);
+});
diff --git a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx
new file mode 100644
index 0000000000000..e2a6ec7dd3947
--- /dev/null
+++ b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx
@@ -0,0 +1,92 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import _ from 'lodash';
+import uuid from 'uuid';
+import { ActionByType, IncompatibleActionError } from '../../ui_actions_plugin';
+import { ViewMode, PanelState, IEmbeddable } from '../../embeddable_plugin';
+import {
+ PanelNotFoundError,
+ EmbeddableInput,
+ isReferenceOrValueEmbeddable,
+} from '../../../../embeddable/public';
+import { DashboardPanelState, DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '..';
+
+export const ACTION_UNLINK_FROM_LIBRARY = 'unlinkFromLibrary';
+
+export interface UnlinkFromLibraryActionContext {
+ embeddable: IEmbeddable;
+}
+
+export class UnlinkFromLibraryAction implements ActionByType {
+ public readonly type = ACTION_UNLINK_FROM_LIBRARY;
+ public readonly id = ACTION_UNLINK_FROM_LIBRARY;
+ public order = 15;
+
+ constructor() {}
+
+ public getDisplayName({ embeddable }: UnlinkFromLibraryActionContext) {
+ if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) {
+ throw new IncompatibleActionError();
+ }
+ return i18n.translate('dashboard.panel.unlinkFromLibrary', {
+ defaultMessage: 'Unlink from library item',
+ });
+ }
+
+ public getIconType({ embeddable }: UnlinkFromLibraryActionContext) {
+ if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) {
+ throw new IncompatibleActionError();
+ }
+ return 'folderExclamation';
+ }
+
+ public async isCompatible({ embeddable }: UnlinkFromLibraryActionContext) {
+ return Boolean(
+ embeddable.getInput()?.viewMode !== ViewMode.VIEW &&
+ embeddable.getRoot() &&
+ embeddable.getRoot().isContainer &&
+ embeddable.getRoot().type === DASHBOARD_CONTAINER_TYPE &&
+ isReferenceOrValueEmbeddable(embeddable) &&
+ embeddable.inputIsRefType(embeddable.getInput())
+ );
+ }
+
+ public async execute({ embeddable }: UnlinkFromLibraryActionContext) {
+ if (!isReferenceOrValueEmbeddable(embeddable)) {
+ throw new IncompatibleActionError();
+ }
+
+ const newInput = await embeddable.getInputAsValueType();
+ embeddable.updateInput(newInput);
+
+ const dashboard = embeddable.getRoot() as DashboardContainer;
+ const panelToReplace = dashboard.getInput().panels[embeddable.id] as DashboardPanelState;
+ if (!panelToReplace) {
+ throw new PanelNotFoundError();
+ }
+
+ const newPanel: PanelState = {
+ type: embeddable.type,
+ explicitInput: { ...newInput, id: uuid.v4() },
+ };
+ dashboard.replacePanel(panelToReplace, newPanel);
+ }
+}
diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
index 3a4e49968626f..7a19514eebe17 100644
--- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
+++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
@@ -468,9 +468,14 @@ export class DashboardAppController {
const explicitInput = {
savedVis: input,
};
+ const embeddableId =
+ 'embeddableId' in incomingEmbeddable
+ ? incomingEmbeddable.embeddableId
+ : undefined;
container.addOrUpdateEmbeddable(
incomingEmbeddable.type,
- explicitInput
+ explicitInput,
+ embeddableId
);
}
}
diff --git a/src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx b/src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx
index 933475d354cfa..0a49e524d3350 100644
--- a/src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx
+++ b/src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx
@@ -19,7 +19,6 @@
import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { coreMock } from '../../../../core/public/mocks';
diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
index ff74580ba256b..036880a1d088b 100644
--- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
@@ -171,6 +171,7 @@ export class DashboardContainer extends Container = IEmbeddable
- >(type: string, explicitInput: Partial) {
- if (explicitInput.id && this.input.panels[explicitInput.id]) {
- this.replacePanel(this.input.panels[explicitInput.id], {
+ >(type: string, explicitInput: Partial, embeddableId?: string) {
+ const idToReplace = embeddableId || explicitInput.id;
+ if (idToReplace && this.input.panels[idToReplace]) {
+ this.replacePanel(this.input.panels[idToReplace], {
type,
explicitInput: {
...explicitInput,
diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx
index 1e07c610b0ef2..60395bce678c2 100644
--- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx
@@ -17,7 +17,6 @@
* under the License.
*/
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import React from 'react';
import { skip } from 'rxjs/operators';
diff --git a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap
index 7985d34b117f5..82c90530c2b4c 100644
--- a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap
+++ b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap
@@ -11,6 +11,7 @@ exports[`after fetch hideWriteControls 1`] = `
findItems={[Function]}
headingId="dashboardListingHeading"
initialFilter=""
+ initialPageSize={10}
listingLimit={1}
noItemsFragment={
@@ -68,6 +69,7 @@ exports[`after fetch initialFilter 1`] = `
findItems={[Function]}
headingId="dashboardListingHeading"
initialFilter="my dashboard"
+ initialPageSize={10}
listingLimit={1000}
noItemsFragment={
@@ -169,6 +171,7 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = `
findItems={[Function]}
headingId="dashboardListingHeading"
initialFilter=""
+ initialPageSize={10}
listingLimit={1}
noItemsFragment={
@@ -270,6 +273,7 @@ exports[`after fetch renders table rows 1`] = `
findItems={[Function]}
headingId="dashboardListingHeading"
initialFilter=""
+ initialPageSize={10}
listingLimit={1000}
noItemsFragment={
@@ -371,6 +375,7 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = `
findItems={[Function]}
headingId="dashboardListingHeading"
initialFilter=""
+ initialPageSize={10}
listingLimit={1}
noItemsFragment={
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.js b/src/plugins/dashboard/public/application/listing/dashboard_listing.js
index c8cb551fbe561..1a7a6b1d75234 100644
--- a/src/plugins/dashboard/public/application/listing/dashboard_listing.js
+++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.js
@@ -115,7 +115,7 @@ export class DashboardListing extends React.Component {
sampleDataInstallLink: (
- this.props.core.application.navigateTo('home', {
+ this.props.core.application.navigateToApp('home', {
path: '#/tutorial_directory/sampleData',
})
}
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js
index dccac4e7c3c76..6acb491f9a20c 100644
--- a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js
+++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js
@@ -79,6 +79,7 @@ describe('after fetch', () => {
getViewUrl={() => {}}
listingLimit={1000}
hideWriteControls={false}
+ initialPageSize={10}
initialFilter="my dashboard"
core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }}
/>
@@ -101,6 +102,7 @@ describe('after fetch', () => {
editItem={() => {}}
getViewUrl={() => {}}
listingLimit={1000}
+ initialPageSize={10}
hideWriteControls={false}
core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }}
/>
@@ -123,6 +125,7 @@ describe('after fetch', () => {
editItem={() => {}}
getViewUrl={() => {}}
listingLimit={1}
+ initialPageSize={10}
hideWriteControls={false}
core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }}
/>
@@ -145,6 +148,7 @@ describe('after fetch', () => {
editItem={() => {}}
getViewUrl={() => {}}
listingLimit={1}
+ initialPageSize={10}
hideWriteControls={true}
core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }}
/>
@@ -167,6 +171,7 @@ describe('after fetch', () => {
editItem={() => {}}
getViewUrl={() => {}}
listingLimit={1}
+ initialPageSize={10}
hideWriteControls={false}
core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }}
/>
diff --git a/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx
index 6eb85faeea014..24075e0a634ba 100644
--- a/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx
+++ b/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx
@@ -17,7 +17,6 @@
* under the License.
*/
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import React from 'react';
import { mount } from 'enzyme';
diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx
index 3b0863a9f4651..2a36f2d801850 100644
--- a/src/plugins/dashboard/public/plugin.tsx
+++ b/src/plugins/dashboard/public/plugin.tsx
@@ -80,6 +80,9 @@ import {
RenderDeps,
ReplacePanelAction,
ReplacePanelActionContext,
+ ACTION_UNLINK_FROM_LIBRARY,
+ UnlinkFromLibraryActionContext,
+ UnlinkFromLibraryAction,
} from './application';
import {
createDashboardUrlGenerator,
@@ -152,6 +155,7 @@ declare module '../../../plugins/ui_actions/public' {
[ACTION_EXPAND_PANEL]: ExpandPanelActionContext;
[ACTION_REPLACE_PANEL]: ReplacePanelActionContext;
[ACTION_CLONE_PANEL]: ClonePanelActionContext;
+ [ACTION_UNLINK_FROM_LIBRARY]: UnlinkFromLibraryActionContext;
}
}
@@ -163,6 +167,7 @@ export class DashboardPlugin
private stopUrlTracking: (() => void) | undefined = undefined;
private getActiveUrl: (() => string) | undefined = undefined;
private currentHistory: ScopedHistory | undefined = undefined;
+ private dashboardFeatureFlagConfig?: DashboardFeatureFlagConfig;
private dashboardUrlGenerator?: DashboardUrlGenerator;
@@ -170,6 +175,9 @@ export class DashboardPlugin
core: CoreSetup,
{ share, uiActions, embeddable, home, kibanaLegacy, data, usageCollection }: SetupDependencies
): Setup {
+ this.dashboardFeatureFlagConfig = this.initializerContext.config.get<
+ DashboardFeatureFlagConfig
+ >();
const expandPanelAction = new ExpandPanelAction();
uiActions.registerAction(expandPanelAction);
uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id);
@@ -415,6 +423,12 @@ export class DashboardPlugin
uiActions.registerAction(clonePanelAction);
uiActions.attachAction(CONTEXT_MENU_TRIGGER, clonePanelAction.id);
+ if (this.dashboardFeatureFlagConfig?.allowByValueEmbeddables) {
+ const unlinkFromLibraryAction = new UnlinkFromLibraryAction();
+ uiActions.registerAction(unlinkFromLibraryAction);
+ uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkFromLibraryAction.id);
+ }
+
const savedDashboardLoader = createSavedDashboardLoader({
savedObjectsClient: core.savedObjects.client,
indexPatterns,
@@ -430,7 +444,7 @@ export class DashboardPlugin
getSavedDashboardLoader: () => savedDashboardLoader,
addEmbeddableToDashboard: this.addEmbeddableToDashboard.bind(this, core),
dashboardUrlGenerator: this.dashboardUrlGenerator,
- dashboardFeatureFlagConfig: this.initializerContext.config.get(),
+ dashboardFeatureFlagConfig: this.dashboardFeatureFlagConfig!,
DashboardContainerByValueRenderer: createDashboardContainerByValueRenderer({
factory: dashboardContainerFactory,
}),
diff --git a/src/plugins/data/common/field_formats/converters/source.ts b/src/plugins/data/common/field_formats/converters/source.ts
index f00261e00971a..9c81bb011e127 100644
--- a/src/plugins/data/common/field_formats/converters/source.ts
+++ b/src/plugins/data/common/field_formats/converters/source.ts
@@ -22,7 +22,7 @@ import { shortenDottedString } from '../../utils';
import { KBN_FIELD_TYPES } from '../../kbn_field_types/types';
import { FieldFormat } from '../field_format';
import { TextContextTypeConvert, HtmlContextTypeConvert, FIELD_FORMAT_IDS } from '../types';
-import { UI_SETTINGS } from '../../';
+import { UI_SETTINGS } from '../../constants';
/**
* Remove all of the whitespace between html tags
diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts
index 84bedd2f9dee0..4b847ebc358d7 100644
--- a/src/plugins/data/common/field_formats/field_formats_registry.ts
+++ b/src/plugins/data/common/field_formats/field_formats_registry.ts
@@ -33,7 +33,7 @@ import { baseFormatters } from './constants/base_formatters';
import { FieldFormat } from './field_format';
import { SerializedFieldFormat } from '../../../expressions/common/types';
import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../types';
-import { UI_SETTINGS } from '../';
+import { UI_SETTINGS } from '../constants';
export class FieldFormatsRegistry {
protected fieldFormats: Map = new Map();
diff --git a/src/plugins/data/common/field_formats/mocks.ts b/src/plugins/data/common/field_formats/mocks.ts
index 9bbaefe2d146a..ddf9cf246ec7d 100644
--- a/src/plugins/data/common/field_formats/mocks.ts
+++ b/src/plugins/data/common/field_formats/mocks.ts
@@ -24,6 +24,7 @@ export const fieldFormatsMock: IFieldFormatsRegistry = {
getByFieldType: jest.fn(),
getDefaultConfig: jest.fn(),
getDefaultInstance: jest.fn().mockImplementation(() => ({
+ convert: jest.fn().mockImplementation((t: string) => t),
getConverterFor: jest.fn().mockImplementation(() => (t: string) => t),
})) as any,
getDefaultInstanceCacheResolver: jest.fn(),
diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts
index ca6bc965d48c5..bc7080e7d450b 100644
--- a/src/plugins/data/common/index.ts
+++ b/src/plugins/data/common/index.ts
@@ -25,7 +25,5 @@ export * from './index_patterns';
export * from './kbn_field_types';
export * from './query';
export * from './search';
-export * from './search/aggs';
-export * from './search/expressions';
export * from './types';
export * from './utils';
diff --git a/src/plugins/data/common/index_patterns/fields/field_list.ts b/src/plugins/data/common/index_patterns/fields/field_list.ts
index 172da9f9ca43f..34bd69230a2e4 100644
--- a/src/plugins/data/common/index_patterns/fields/field_list.ts
+++ b/src/plugins/data/common/index_patterns/fields/field_list.ts
@@ -18,10 +18,11 @@
*/
import { findIndex } from 'lodash';
-import { IFieldType, shortenDottedString } from '../../../common';
+import { IFieldType } from './types';
import { IndexPatternField } from './index_pattern_field';
import { OnNotification, FieldSpec } from '../types';
import { IndexPattern } from '../index_patterns';
+import { shortenDottedString } from '../../utils';
type FieldMap = Map;
diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts
index 679de103f8019..965f1a7f63065 100644
--- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts
+++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts
@@ -18,13 +18,10 @@
*/
import { i18n } from '@kbn/i18n';
-import {
- IFieldType,
- KbnFieldType,
- getKbnFieldType,
- KBN_FIELD_TYPES,
- FieldFormat,
-} from '../../../common';
+import { KbnFieldType, getKbnFieldType } from '../../kbn_field_types';
+import { KBN_FIELD_TYPES } from '../../kbn_field_types/types';
+import { FieldFormat } from '../../field_formats';
+import { IFieldType } from './types';
import { OnNotification, FieldSpec } from '../types';
import { IndexPattern } from '../index_patterns';
diff --git a/src/plugins/data/public/search/aggs/agg_config.test.ts b/src/plugins/data/common/search/aggs/agg_config.test.ts
similarity index 98%
rename from src/plugins/data/public/search/aggs/agg_config.test.ts
rename to src/plugins/data/common/search/aggs/agg_config.test.ts
index f9279e06d14a9..a443eacee731c 100644
--- a/src/plugins/data/public/search/aggs/agg_config.test.ts
+++ b/src/plugins/data/common/search/aggs/agg_config.test.ts
@@ -25,7 +25,8 @@ import { AggType } from './agg_type';
import { AggTypesRegistryStart } from './agg_types_registry';
import { mockAggTypesRegistry } from './test_helpers';
import { MetricAggType } from './metrics/metric_agg_type';
-import { IndexPattern, IIndexPatternFieldList } from '../../index_patterns';
+import { IndexPattern } from '../../index_patterns/index_patterns/index_pattern';
+import { IIndexPatternFieldList } from '../../index_patterns/fields';
describe('AggConfig', () => {
let indexPattern: IndexPattern;
@@ -622,7 +623,7 @@ describe('AggConfig', () => {
it('creates a subexpression for param types other than "agg" which have specified toExpressionAst', () => {
// Overwrite the `ranges` param in the `range` agg with a mock toExpressionAst function
- const range: MetricAggType = typesRegistry.get('range');
+ const range = typesRegistry.get('range') as MetricAggType;
range.expressionName = 'aggRange';
const rangesParam = range.params.find((p) => p.name === 'ranges');
rangesParam!.toExpressionAst = (val: any) => ({
diff --git a/src/plugins/data/public/search/aggs/agg_config.ts b/src/plugins/data/common/search/aggs/agg_config.ts
similarity index 99%
rename from src/plugins/data/public/search/aggs/agg_config.ts
rename to src/plugins/data/common/search/aggs/agg_config.ts
index 31618eac18e98..b5747ce7bb9bd 100644
--- a/src/plugins/data/public/search/aggs/agg_config.ts
+++ b/src/plugins/data/common/search/aggs/agg_config.ts
@@ -20,16 +20,17 @@
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { Assign, Ensure } from '@kbn/utility-types';
+
+import { FetchOptions, ISearchSource } from 'src/plugins/data/public';
import {
ExpressionAstFunction,
ExpressionAstArgument,
SerializedFieldFormat,
} from 'src/plugins/expressions/common';
+
import { IAggType } from './agg_type';
import { writeParams } from './agg_params';
import { IAggConfigs } from './agg_configs';
-import { FetchOptions } from '../fetch';
-import { ISearchSource } from '../search_source';
type State = string | number | boolean | null | undefined | SerializableState;
diff --git a/src/plugins/data/public/search/aggs/agg_configs.test.ts b/src/plugins/data/common/search/aggs/agg_configs.test.ts
similarity index 98%
rename from src/plugins/data/public/search/aggs/agg_configs.test.ts
rename to src/plugins/data/common/search/aggs/agg_configs.test.ts
index ff0cc3341929e..803ccc70b98a7 100644
--- a/src/plugins/data/public/search/aggs/agg_configs.test.ts
+++ b/src/plugins/data/common/search/aggs/agg_configs.test.ts
@@ -22,8 +22,9 @@ import { AggConfig } from './agg_config';
import { AggConfigs } from './agg_configs';
import { AggTypesRegistryStart } from './agg_types_registry';
import { mockAggTypesRegistry } from './test_helpers';
-import { IndexPatternField, IndexPattern } from '../../index_patterns';
-import { stubIndexPattern, stubIndexPatternWithFields } from '../../../public/stubs';
+import type { IndexPatternField } from '../../index_patterns';
+import { IndexPattern } from '../../index_patterns/index_patterns/index_pattern';
+import { stubIndexPattern, stubIndexPatternWithFields } from '../../../common/stubs';
describe('AggConfigs', () => {
let indexPattern: IndexPattern;
diff --git a/src/plugins/data/public/search/aggs/agg_configs.ts b/src/plugins/data/common/search/aggs/agg_configs.ts
similarity index 98%
rename from src/plugins/data/public/search/aggs/agg_configs.ts
rename to src/plugins/data/common/search/aggs/agg_configs.ts
index b272dfd3c7468..203eda3a907ee 100644
--- a/src/plugins/data/public/search/aggs/agg_configs.ts
+++ b/src/plugins/data/common/search/aggs/agg_configs.ts
@@ -20,13 +20,12 @@
import _ from 'lodash';
import { Assign } from '@kbn/utility-types';
+import { FetchOptions, ISearchSource } from 'src/plugins/data/public';
import { AggConfig, AggConfigSerialized, IAggConfig } from './agg_config';
import { IAggType } from './agg_type';
import { AggTypesRegistryStart } from './agg_types_registry';
import { AggGroupNames } from './agg_groups';
-import { IndexPattern } from '../../index_patterns';
-import { ISearchSource } from '../search_source';
-import { FetchOptions } from '../fetch';
+import { IndexPattern } from '../../index_patterns/index_patterns/index_pattern';
import { TimeRange } from '../../../common';
function removeParentAggs(obj: any) {
diff --git a/src/plugins/data/public/search/aggs/agg_groups.ts b/src/plugins/data/common/search/aggs/agg_groups.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/agg_groups.ts
rename to src/plugins/data/common/search/aggs/agg_groups.ts
diff --git a/src/plugins/data/public/search/aggs/agg_params.test.ts b/src/plugins/data/common/search/aggs/agg_params.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/agg_params.test.ts
rename to src/plugins/data/common/search/aggs/agg_params.test.ts
diff --git a/src/plugins/data/public/search/aggs/agg_params.ts b/src/plugins/data/common/search/aggs/agg_params.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/agg_params.ts
rename to src/plugins/data/common/search/aggs/agg_params.ts
diff --git a/src/plugins/data/public/search/aggs/agg_type.test.ts b/src/plugins/data/common/search/aggs/agg_type.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/agg_type.test.ts
rename to src/plugins/data/common/search/aggs/agg_type.test.ts
diff --git a/src/plugins/data/public/search/aggs/agg_type.ts b/src/plugins/data/common/search/aggs/agg_type.ts
similarity index 99%
rename from src/plugins/data/public/search/aggs/agg_type.ts
rename to src/plugins/data/common/search/aggs/agg_type.ts
index de7ca48e71d57..0ba2bb66e7758 100644
--- a/src/plugins/data/public/search/aggs/agg_type.ts
+++ b/src/plugins/data/common/search/aggs/agg_type.ts
@@ -20,6 +20,7 @@
import { constant, noop, identity } from 'lodash';
import { i18n } from '@kbn/i18n';
+import { ISearchSource } from 'src/plugins/data/public';
import { SerializedFieldFormat } from 'src/plugins/expressions/common';
import type { RequestAdapter } from 'src/plugins/inspector/common';
@@ -28,7 +29,6 @@ import { AggConfig } from './agg_config';
import { IAggConfigs } from './agg_configs';
import { BaseParamType } from './param_types/base';
import { AggParamType } from './param_types/agg';
-import { ISearchSource } from '../search_source';
export interface AggTypeConfig<
TAggConfig extends AggConfig = AggConfig,
diff --git a/src/plugins/data/public/search/aggs/agg_types.ts b/src/plugins/data/common/search/aggs/agg_types.ts
similarity index 66%
rename from src/plugins/data/public/search/aggs/agg_types.ts
rename to src/plugins/data/common/search/aggs/agg_types.ts
index 2820ae495f318..8565de13aed5b 100644
--- a/src/plugins/data/public/search/aggs/agg_types.ts
+++ b/src/plugins/data/common/search/aggs/agg_types.ts
@@ -17,9 +17,9 @@
* under the License.
*/
-import { IUiSettingsClient } from 'src/core/public';
-import { TimeRange, TimeRangeBounds } from '../../../common';
-import { GetInternalStartServicesFn } from '../../types';
+import { FieldFormatsStartCommon } from '../../field_formats';
+import { BUCKET_TYPES } from './buckets';
+import { METRIC_TYPES } from './metrics';
import { getCountMetricAgg } from './metrics/count';
import { getAvgMetricAgg } from './metrics/avg';
@@ -39,7 +39,7 @@ import { getCumulativeSumMetricAgg } from './metrics/cumulative_sum';
import { getMovingAvgMetricAgg } from './metrics/moving_avg';
import { getSerialDiffMetricAgg } from './metrics/serial_diff';
-import { getDateHistogramBucketAgg } from './buckets/date_histogram';
+import { getDateHistogramBucketAgg, CalculateBoundsFn } from './buckets/date_histogram';
import { getHistogramBucketAgg } from './buckets/histogram';
import { getRangeBucketAgg } from './buckets/range';
import { getDateRangeBucketAgg } from './buckets/date_range';
@@ -55,52 +55,50 @@ import { getBucketAvgMetricAgg } from './metrics/bucket_avg';
import { getBucketMinMetricAgg } from './metrics/bucket_min';
import { getBucketMaxMetricAgg } from './metrics/bucket_max';
+/** @internal */
export interface AggTypesDependencies {
- calculateBounds: (timeRange: TimeRange) => TimeRangeBounds;
- getInternalStartServices: GetInternalStartServicesFn;
- uiSettings: IUiSettingsClient;
+ calculateBounds: CalculateBoundsFn;
+ getConfig: (key: string) => T;
+ getFieldFormatsStart: () => Pick;
+ isDefaultTimezone: () => boolean;
}
-export const getAggTypes = ({
- calculateBounds,
- getInternalStartServices,
- uiSettings,
-}: AggTypesDependencies) => ({
+export const getAggTypes = () => ({
metrics: [
- getCountMetricAgg(),
- getAvgMetricAgg(),
- getSumMetricAgg(),
- getMedianMetricAgg(),
- getMinMetricAgg(),
- getMaxMetricAgg(),
- getStdDeviationMetricAgg(),
- getCardinalityMetricAgg(),
- getPercentilesMetricAgg(),
- getPercentileRanksMetricAgg({ getInternalStartServices }),
- getTopHitMetricAgg(),
- getDerivativeMetricAgg(),
- getCumulativeSumMetricAgg(),
- getMovingAvgMetricAgg(),
- getSerialDiffMetricAgg(),
- getBucketAvgMetricAgg(),
- getBucketSumMetricAgg(),
- getBucketMinMetricAgg(),
- getBucketMaxMetricAgg(),
- getGeoBoundsMetricAgg(),
- getGeoCentroidMetricAgg(),
+ { name: METRIC_TYPES.COUNT, fn: getCountMetricAgg },
+ { name: METRIC_TYPES.AVG, fn: getAvgMetricAgg },
+ { name: METRIC_TYPES.SUM, fn: getSumMetricAgg },
+ { name: METRIC_TYPES.MEDIAN, fn: getMedianMetricAgg },
+ { name: METRIC_TYPES.MIN, fn: getMinMetricAgg },
+ { name: METRIC_TYPES.MAX, fn: getMaxMetricAgg },
+ { name: METRIC_TYPES.STD_DEV, fn: getStdDeviationMetricAgg },
+ { name: METRIC_TYPES.CARDINALITY, fn: getCardinalityMetricAgg },
+ { name: METRIC_TYPES.PERCENTILES, fn: getPercentilesMetricAgg },
+ { name: METRIC_TYPES.PERCENTILE_RANKS, fn: getPercentileRanksMetricAgg },
+ { name: METRIC_TYPES.TOP_HITS, fn: getTopHitMetricAgg },
+ { name: METRIC_TYPES.DERIVATIVE, fn: getDerivativeMetricAgg },
+ { name: METRIC_TYPES.CUMULATIVE_SUM, fn: getCumulativeSumMetricAgg },
+ { name: METRIC_TYPES.MOVING_FN, fn: getMovingAvgMetricAgg },
+ { name: METRIC_TYPES.SERIAL_DIFF, fn: getSerialDiffMetricAgg },
+ { name: METRIC_TYPES.AVG_BUCKET, fn: getBucketAvgMetricAgg },
+ { name: METRIC_TYPES.SUM_BUCKET, fn: getBucketSumMetricAgg },
+ { name: METRIC_TYPES.MIN_BUCKET, fn: getBucketMinMetricAgg },
+ { name: METRIC_TYPES.MAX_BUCKET, fn: getBucketMaxMetricAgg },
+ { name: METRIC_TYPES.GEO_BOUNDS, fn: getGeoBoundsMetricAgg },
+ { name: METRIC_TYPES.GEO_CENTROID, fn: getGeoCentroidMetricAgg },
],
buckets: [
- getDateHistogramBucketAgg({ calculateBounds, uiSettings }),
- getHistogramBucketAgg({ uiSettings, getInternalStartServices }),
- getRangeBucketAgg({ getInternalStartServices }),
- getDateRangeBucketAgg({ uiSettings }),
- getIpRangeBucketAgg(),
- getTermsBucketAgg(),
- getFilterBucketAgg(),
- getFiltersBucketAgg({ uiSettings }),
- getSignificantTermsBucketAgg(),
- getGeoHashBucketAgg(),
- getGeoTitleBucketAgg(),
+ { name: BUCKET_TYPES.DATE_HISTOGRAM, fn: getDateHistogramBucketAgg },
+ { name: BUCKET_TYPES.HISTOGRAM, fn: getHistogramBucketAgg },
+ { name: BUCKET_TYPES.RANGE, fn: getRangeBucketAgg },
+ { name: BUCKET_TYPES.DATE_RANGE, fn: getDateRangeBucketAgg },
+ { name: BUCKET_TYPES.IP_RANGE, fn: getIpRangeBucketAgg },
+ { name: BUCKET_TYPES.TERMS, fn: getTermsBucketAgg },
+ { name: BUCKET_TYPES.FILTER, fn: getFilterBucketAgg },
+ { name: BUCKET_TYPES.FILTERS, fn: getFiltersBucketAgg },
+ { name: BUCKET_TYPES.SIGNIFICANT_TERMS, fn: getSignificantTermsBucketAgg },
+ { name: BUCKET_TYPES.GEOHASH_GRID, fn: getGeoHashBucketAgg },
+ { name: BUCKET_TYPES.GEOTILE_GRID, fn: getGeoTitleBucketAgg },
],
});
diff --git a/src/plugins/data/public/search/aggs/agg_types_registry.test.ts b/src/plugins/data/common/search/aggs/agg_types_registry.test.ts
similarity index 54%
rename from src/plugins/data/public/search/aggs/agg_types_registry.test.ts
rename to src/plugins/data/common/search/aggs/agg_types_registry.test.ts
index 58d1a07d965e2..df3dddfcd9c6f 100644
--- a/src/plugins/data/public/search/aggs/agg_types_registry.test.ts
+++ b/src/plugins/data/common/search/aggs/agg_types_registry.test.ts
@@ -17,21 +17,17 @@
* under the License.
*/
-import {
- AggTypesRegistry,
- AggTypesRegistrySetup,
- AggTypesRegistryStart,
-} from './agg_types_registry';
+import { AggTypesRegistry, AggTypesRegistrySetup } from './agg_types_registry';
import { BucketAggType } from './buckets/bucket_agg_type';
import { MetricAggType } from './metrics/metric_agg_type';
-const bucketType = { name: 'terms', type: 'bucket' } as BucketAggType;
-const metricType = { name: 'count', type: 'metric' } as MetricAggType;
+const bucketType = () => ({ name: 'terms', type: 'buckets' } as BucketAggType);
+const metricType = () => ({ name: 'count', type: 'metrics' } as MetricAggType);
describe('AggTypesRegistry', () => {
let registry: AggTypesRegistry;
let setup: AggTypesRegistrySetup;
- let start: AggTypesRegistryStart;
+ let start: ReturnType;
beforeEach(() => {
registry = new AggTypesRegistry();
@@ -40,49 +36,53 @@ describe('AggTypesRegistry', () => {
});
it('registerBucket adds new buckets', () => {
- setup.registerBucket(bucketType);
- expect(start.getBuckets()).toEqual([bucketType]);
+ setup.registerBucket('terms', bucketType);
+ expect(start.getAll().buckets).toEqual([bucketType]);
});
it('registerBucket throws error when registering duplicate bucket', () => {
expect(() => {
- setup.registerBucket(bucketType);
- setup.registerBucket(bucketType);
+ setup.registerBucket('terms', bucketType);
+ setup.registerBucket('terms', bucketType);
}).toThrow(/already been registered with name: terms/);
+
+ const fooBucket = () => ({ name: 'foo', type: 'buckets' } as BucketAggType);
+ const fooMetric = () => ({ name: 'foo', type: 'metrics' } as MetricAggType);
+ expect(() => {
+ setup.registerBucket('foo', fooBucket);
+ setup.registerMetric('foo', fooMetric);
+ }).toThrow(/already been registered with name: foo/);
});
it('registerMetric adds new metrics', () => {
- setup.registerMetric(metricType);
- expect(start.getMetrics()).toEqual([metricType]);
+ setup.registerMetric('count', metricType);
+ expect(start.getAll().metrics).toEqual([metricType]);
});
it('registerMetric throws error when registering duplicate metric', () => {
expect(() => {
- setup.registerMetric(metricType);
- setup.registerMetric(metricType);
+ setup.registerMetric('count', metricType);
+ setup.registerMetric('count', metricType);
}).toThrow(/already been registered with name: count/);
+
+ const fooBucket = () => ({ name: 'foo', type: 'buckets' } as BucketAggType);
+ const fooMetric = () => ({ name: 'foo', type: 'metrics' } as MetricAggType);
+ expect(() => {
+ setup.registerMetric('foo', fooMetric);
+ setup.registerBucket('foo', fooBucket);
+ }).toThrow(/already been registered with name: foo/);
});
it('gets either buckets or metrics by id', () => {
- setup.registerBucket(bucketType);
- setup.registerMetric(metricType);
+ setup.registerBucket('terms', bucketType);
+ setup.registerMetric('count', metricType);
expect(start.get('terms')).toEqual(bucketType);
expect(start.get('count')).toEqual(metricType);
});
- it('getBuckets retrieves only buckets', () => {
- setup.registerBucket(bucketType);
- expect(start.getBuckets()).toEqual([bucketType]);
- });
-
- it('getMetrics retrieves only metrics', () => {
- setup.registerMetric(metricType);
- expect(start.getMetrics()).toEqual([metricType]);
- });
-
it('getAll returns all buckets and metrics', () => {
- setup.registerBucket(bucketType);
- setup.registerMetric(metricType);
+ setup.registerBucket('terms', bucketType);
+ setup.registerMetric('count', metricType);
expect(start.getAll()).toEqual({
buckets: [bucketType],
metrics: [metricType],
diff --git a/src/plugins/data/public/search/aggs/agg_types_registry.ts b/src/plugins/data/common/search/aggs/agg_types_registry.ts
similarity index 57%
rename from src/plugins/data/public/search/aggs/agg_types_registry.ts
rename to src/plugins/data/common/search/aggs/agg_types_registry.ts
index 5a0c58120d810..ce22fa840bafa 100644
--- a/src/plugins/data/public/search/aggs/agg_types_registry.ts
+++ b/src/plugins/data/common/search/aggs/agg_types_registry.ts
@@ -19,9 +19,21 @@
import { BucketAggType } from './buckets/bucket_agg_type';
import { MetricAggType } from './metrics/metric_agg_type';
+import { AggTypesDependencies } from './agg_types';
export type AggTypesRegistrySetup = ReturnType;
-export type AggTypesRegistryStart = ReturnType;
+/**
+ * AggsCommonStart returns the _unitialized_ agg type providers, but in our
+ * real start contract we will need to return the initialized versions.
+ * So we need to provide the correct typings so they can be overwritten
+ * on client/server.
+ *
+ * @internal
+ */
+export interface AggTypesRegistryStart {
+ get: (id: string) => BucketAggType | MetricAggType;
+ getAll: () => { buckets: Array>; metrics: Array> };
+}
export class AggTypesRegistry {
private readonly bucketAggs = new Map();
@@ -29,17 +41,27 @@ export class AggTypesRegistry {
setup = () => {
return {
- registerBucket: >(type: T): void => {
- const { name } = type;
- if (this.bucketAggs.get(name)) {
- throw new Error(`Bucket agg has already been registered with name: ${name}`);
+ registerBucket: <
+ N extends string,
+ T extends (deps: AggTypesDependencies) => BucketAggType
+ >(
+ name: N,
+ type: T
+ ): void => {
+ if (this.bucketAggs.get(name) || this.metricAggs.get(name)) {
+ throw new Error(`Agg has already been registered with name: ${name}`);
}
this.bucketAggs.set(name, type);
},
- registerMetric: >(type: T): void => {
- const { name } = type;
- if (this.metricAggs.get(name)) {
- throw new Error(`Metric agg has already been registered with name: ${name}`);
+ registerMetric: <
+ N extends string,
+ T extends (deps: AggTypesDependencies) => MetricAggType
+ >(
+ name: N,
+ type: T
+ ): void => {
+ if (this.bucketAggs.get(name) || this.metricAggs.get(name)) {
+ throw new Error(`Agg has already been registered with name: ${name}`);
}
this.metricAggs.set(name, type);
},
@@ -51,12 +73,6 @@ export class AggTypesRegistry {
get: (name: string) => {
return this.bucketAggs.get(name) || this.metricAggs.get(name);
},
- getBuckets: () => {
- return Array.from(this.bucketAggs.values());
- },
- getMetrics: () => {
- return Array.from(this.metricAggs.values());
- },
getAll: () => {
return {
buckets: Array.from(this.bucketAggs.values()),
diff --git a/src/plugins/data/common/search/aggs/aggs_service.test.ts b/src/plugins/data/common/search/aggs/aggs_service.test.ts
new file mode 100644
index 0000000000000..bcf2101704c80
--- /dev/null
+++ b/src/plugins/data/common/search/aggs/aggs_service.test.ts
@@ -0,0 +1,217 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ AggsCommonService,
+ AggsCommonSetupDependencies,
+ AggsCommonStartDependencies,
+} from './aggs_service';
+import { AggTypesDependencies, getAggTypes } from './agg_types';
+import { BucketAggType } from './buckets/bucket_agg_type';
+import { MetricAggType } from './metrics/metric_agg_type';
+
+describe('Aggs service', () => {
+ let service: AggsCommonService;
+ let setupDeps: AggsCommonSetupDependencies;
+ let startDeps: AggsCommonStartDependencies;
+ const aggTypesDependencies: AggTypesDependencies = {
+ calculateBounds: jest.fn(),
+ getFieldFormatsStart: jest.fn(),
+ getConfig: jest.fn(),
+ isDefaultTimezone: () => true,
+ };
+
+ beforeEach(() => {
+ service = new AggsCommonService();
+ setupDeps = {
+ registerFunction: jest.fn(),
+ };
+ startDeps = {
+ getConfig: jest.fn(),
+ };
+ });
+
+ describe('setup()', () => {
+ test('exposes proper contract', () => {
+ const setup = service.setup(setupDeps);
+ expect(Object.keys(setup).length).toBe(1);
+ expect(setup).toHaveProperty('types');
+ });
+
+ test('instantiates a new registry', () => {
+ const a = new AggsCommonService();
+ const b = new AggsCommonService();
+ const bSetupDeps = {
+ registerFunction: jest.fn(),
+ };
+
+ const aSetup = a.setup(setupDeps);
+ aSetup.types.registerBucket(
+ 'foo',
+ () => ({ name: 'foo', type: 'buckets' } as BucketAggType)
+ );
+ const aStart = a.start(startDeps);
+ expect(aStart.types.getAll().buckets.map((t) => t(aggTypesDependencies).name))
+ .toMatchInlineSnapshot(`
+ Array [
+ "date_histogram",
+ "histogram",
+ "range",
+ "date_range",
+ "ip_range",
+ "terms",
+ "filter",
+ "filters",
+ "significant_terms",
+ "geohash_grid",
+ "geotile_grid",
+ "foo",
+ ]
+ `);
+ expect(aStart.types.getAll().metrics.map((t) => t(aggTypesDependencies).name))
+ .toMatchInlineSnapshot(`
+ Array [
+ "count",
+ "avg",
+ "sum",
+ "median",
+ "min",
+ "max",
+ "std_dev",
+ "cardinality",
+ "percentiles",
+ "percentile_ranks",
+ "top_hits",
+ "derivative",
+ "cumulative_sum",
+ "moving_avg",
+ "serial_diff",
+ "avg_bucket",
+ "sum_bucket",
+ "min_bucket",
+ "max_bucket",
+ "geo_bounds",
+ "geo_centroid",
+ ]
+ `);
+
+ b.setup(bSetupDeps);
+ const bStart = b.start(startDeps);
+ expect(bStart.types.getAll().buckets.map((t) => t(aggTypesDependencies).name))
+ .toMatchInlineSnapshot(`
+ Array [
+ "date_histogram",
+ "histogram",
+ "range",
+ "date_range",
+ "ip_range",
+ "terms",
+ "filter",
+ "filters",
+ "significant_terms",
+ "geohash_grid",
+ "geotile_grid",
+ ]
+ `);
+ expect(bStart.types.getAll().metrics.map((t) => t(aggTypesDependencies).name))
+ .toMatchInlineSnapshot(`
+ Array [
+ "count",
+ "avg",
+ "sum",
+ "median",
+ "min",
+ "max",
+ "std_dev",
+ "cardinality",
+ "percentiles",
+ "percentile_ranks",
+ "top_hits",
+ "derivative",
+ "cumulative_sum",
+ "moving_avg",
+ "serial_diff",
+ "avg_bucket",
+ "sum_bucket",
+ "min_bucket",
+ "max_bucket",
+ "geo_bounds",
+ "geo_centroid",
+ ]
+ `);
+ });
+
+ test('registers default agg types', () => {
+ service.setup(setupDeps);
+ const start = service.start(startDeps);
+
+ const aggTypes = getAggTypes();
+ expect(start.types.getAll().buckets.length).toBe(aggTypes.buckets.length);
+ expect(start.types.getAll().metrics.length).toBe(aggTypes.metrics.length);
+ });
+
+ test('merges default agg types with types registered during setup', () => {
+ const setup = service.setup(setupDeps);
+ setup.types.registerBucket(
+ 'foo',
+ () => ({ name: 'foo', type: 'buckets' } as BucketAggType)
+ );
+ setup.types.registerMetric(
+ 'bar',
+ () => ({ name: 'bar', type: 'metrics' } as MetricAggType)
+ );
+ const start = service.start(startDeps);
+
+ const aggTypes = getAggTypes();
+ expect(start.types.getAll().buckets.length).toBe(aggTypes.buckets.length + 1);
+ expect(start.types.getAll().buckets.some((t) => t(aggTypesDependencies).name === 'foo')).toBe(
+ true
+ );
+ expect(start.types.getAll().metrics.length).toBe(aggTypes.metrics.length + 1);
+ expect(start.types.getAll().metrics.some((t) => t(aggTypesDependencies).name === 'bar')).toBe(
+ true
+ );
+ });
+
+ test('registers all agg type expression functions', () => {
+ service.setup(setupDeps);
+ const aggTypes = getAggTypes();
+ expect(setupDeps.registerFunction).toHaveBeenCalledTimes(
+ aggTypes.buckets.length + aggTypes.metrics.length
+ );
+ });
+ });
+
+ describe('start()', () => {
+ test('exposes proper contract', () => {
+ const start = service.start(startDeps);
+ expect(Object.keys(start).length).toBe(3);
+ expect(start).toHaveProperty('calculateAutoTimeExpression');
+ expect(start).toHaveProperty('createAggConfigs');
+ expect(start).toHaveProperty('types');
+ });
+
+ test('types registry returns uninitialized type providers', () => {
+ service.setup(setupDeps);
+ const start = service.start(startDeps);
+ expect(typeof start.types.get('terms')).toBe('function');
+ expect(start.types.get('terms')(aggTypesDependencies).name).toBe('terms');
+ });
+ });
+});
diff --git a/src/plugins/data/common/search/aggs/aggs_service.ts b/src/plugins/data/common/search/aggs/aggs_service.ts
new file mode 100644
index 0000000000000..59c54fcce6838
--- /dev/null
+++ b/src/plugins/data/common/search/aggs/aggs_service.ts
@@ -0,0 +1,92 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ExpressionsServiceSetup } from 'src/plugins/expressions/common';
+import { UI_SETTINGS } from '../../../common';
+import {
+ AggConfigs,
+ AggTypesRegistry,
+ getAggTypes,
+ getAggTypesFunctions,
+ getCalculateAutoTimeExpression,
+} from './';
+import { AggsCommonSetup, AggsCommonStart } from './types';
+
+/** @internal */
+export const aggsRequiredUiSettings = [
+ 'dateFormat',
+ 'dateFormat:scaled',
+ 'dateFormat:tz',
+ UI_SETTINGS.HISTOGRAM_BAR_TARGET,
+ UI_SETTINGS.HISTOGRAM_MAX_BARS,
+ UI_SETTINGS.SEARCH_QUERY_LANGUAGE,
+ UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS,
+ UI_SETTINGS.QUERY_STRING_OPTIONS,
+ UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX,
+];
+
+/** @internal */
+export interface AggsCommonSetupDependencies {
+ registerFunction: ExpressionsServiceSetup['registerFunction'];
+}
+
+/** @internal */
+export interface AggsCommonStartDependencies {
+ getConfig: (key: string) => T;
+}
+
+/**
+ * The aggs service provides a means of modeling and manipulating the various
+ * Elasticsearch aggregations supported by Kibana, providing the ability to
+ * output the correct DSL when you are ready to send your request to ES.
+ */
+export class AggsCommonService {
+ private readonly aggTypesRegistry = new AggTypesRegistry();
+
+ public setup({ registerFunction }: AggsCommonSetupDependencies): AggsCommonSetup {
+ const aggTypesSetup = this.aggTypesRegistry.setup();
+
+ // register each agg type
+ const aggTypes = getAggTypes();
+ aggTypes.buckets.forEach(({ name, fn }) => aggTypesSetup.registerBucket(name, fn));
+ aggTypes.metrics.forEach(({ name, fn }) => aggTypesSetup.registerMetric(name, fn));
+
+ // register expression functions for each agg type
+ const aggFunctions = getAggTypesFunctions();
+ aggFunctions.forEach((fn) => registerFunction(fn));
+
+ return {
+ types: aggTypesSetup,
+ };
+ }
+
+ public start({ getConfig }: AggsCommonStartDependencies): AggsCommonStart {
+ const aggTypesStart = this.aggTypesRegistry.start();
+
+ return {
+ calculateAutoTimeExpression: getCalculateAutoTimeExpression(getConfig),
+ createAggConfigs: (indexPattern, configStates = [], schemas) => {
+ return new AggConfigs(indexPattern, configStates, {
+ typesRegistry: aggTypesStart,
+ });
+ },
+ types: aggTypesStart,
+ };
+ }
+}
diff --git a/src/plugins/data/public/search/aggs/buckets/_interval_options.ts b/src/plugins/data/common/search/aggs/buckets/_interval_options.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/_interval_options.ts
rename to src/plugins/data/common/search/aggs/buckets/_interval_options.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts
rename to src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts
rename to src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts b/src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts
rename to src/plugins/data/common/search/aggs/buckets/bucket_agg_type.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/bucket_agg_types.ts b/src/plugins/data/common/search/aggs/buckets/bucket_agg_types.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/bucket_agg_types.ts
rename to src/plugins/data/common/search/aggs/buckets/bucket_agg_types.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/date_histogram.test.ts
similarity index 87%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/date_histogram.test.ts
index 24a17b60566cc..143d549836900 100644
--- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/create_filter/date_histogram.test.ts
@@ -22,32 +22,17 @@ import { createFilterDateHistogram } from './date_histogram';
import { intervalOptions } from '../_interval_options';
import { AggConfigs } from '../../agg_configs';
import { mockAggTypesRegistry } from '../../test_helpers';
-import {
- getDateHistogramBucketAgg,
- DateHistogramBucketAggDependencies,
- IBucketDateHistogramAggConfig,
-} from '../date_histogram';
+import { IBucketDateHistogramAggConfig } from '../date_histogram';
import { BUCKET_TYPES } from '../bucket_agg_types';
import { RangeFilter } from '../../../../../common';
-import { coreMock } from '../../../../../../../core/public/mocks';
describe('AggConfig Filters', () => {
describe('date_histogram', () => {
- let aggTypesDependencies: DateHistogramBucketAggDependencies;
let agg: IBucketDateHistogramAggConfig;
let filter: RangeFilter;
let bucketStart: any;
let field: any;
- beforeEach(() => {
- const { uiSettings } = coreMock.createSetup();
-
- aggTypesDependencies = {
- calculateBounds: jest.fn(),
- uiSettings,
- };
- });
-
const init = (interval: string = 'auto', duration: any = moment.duration(15, 'minutes')) => {
field = {
name: 'date',
@@ -71,7 +56,7 @@ describe('AggConfig Filters', () => {
},
],
{
- typesRegistry: mockAggTypesRegistry([getDateHistogramBucketAgg(aggTypesDependencies)]),
+ typesRegistry: mockAggTypesRegistry(),
}
);
const bucketKey = 1422579600000;
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/date_histogram.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/date_histogram.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/date_range.test.ts
similarity index 78%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/date_range.test.ts
index c272c037c5927..8def27f1d8ee5 100644
--- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/create_filter/date_range.test.ts
@@ -18,31 +18,18 @@
*/
import moment from 'moment';
-import { getDateRangeBucketAgg, DateRangeBucketAggDependencies } from '../date_range';
import { createFilterDateRange } from './date_range';
-import { FieldFormatsGetConfigFn } from '../../../../../common';
-import { DateFormat } from '../../../../field_formats';
import { AggConfigs } from '../../agg_configs';
import { mockAggTypesRegistry } from '../../test_helpers';
import { BUCKET_TYPES } from '../bucket_agg_types';
import { IBucketAggConfig } from '../bucket_agg_type';
-import { coreMock } from '../../../../../../../core/public/mocks';
describe('AggConfig Filters', () => {
describe('Date range', () => {
- let aggTypesDependencies: DateRangeBucketAggDependencies;
-
- beforeEach(() => {
- const { uiSettings } = coreMock.createSetup();
-
- aggTypesDependencies = { uiSettings };
- });
-
- const getConfig = (() => {}) as FieldFormatsGetConfigFn;
const getAggConfigs = () => {
const field = {
name: '@timestamp',
- format: new DateFormat({}, getConfig),
+ format: {},
};
const indexPattern = {
@@ -66,7 +53,7 @@ describe('AggConfig Filters', () => {
},
],
{
- typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]),
+ typesRegistry: mockAggTypesRegistry(),
}
);
};
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/date_range.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/date_range.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/filters.test.ts
similarity index 83%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/filters.test.ts
index ff66d80c6d8d0..aec99bd00af55 100644
--- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/create_filter/filters.test.ts
@@ -17,23 +17,13 @@
* under the License.
*/
-import { getFiltersBucketAgg, FiltersBucketAggDependencies } from '../filters';
import { createFilterFilters } from './filters';
import { AggConfigs } from '../../agg_configs';
import { mockAggTypesRegistry } from '../../test_helpers';
import { IBucketAggConfig } from '../bucket_agg_type';
-import { coreMock } from '../../../../../../../core/public/mocks';
describe('AggConfig Filters', () => {
describe('filters', () => {
- let aggTypesDependencies: FiltersBucketAggDependencies;
-
- beforeEach(() => {
- const { uiSettings } = coreMock.createSetup();
-
- aggTypesDependencies = { uiSettings };
- });
-
const getAggConfigs = () => {
const field = {
name: 'bytes',
@@ -63,7 +53,7 @@ describe('AggConfig Filters', () => {
},
],
{
- typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]),
+ typesRegistry: mockAggTypesRegistry(),
}
);
};
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/filters.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/filters.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts
similarity index 77%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts
index 3f9f5dd5672f0..b57d530ef40e8 100644
--- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts
@@ -17,25 +17,14 @@
* under the License.
*/
-import { createFilterHistogram } from './histogram';
+import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common/field_formats';
import { AggConfigs } from '../../agg_configs';
-import { mockAggTypesRegistry } from '../../test_helpers';
+import { mockAggTypesRegistry, mockGetFieldFormatsStart } from '../../test_helpers';
import { BUCKET_TYPES } from '../bucket_agg_types';
import { IBucketAggConfig } from '../bucket_agg_type';
-import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common';
-import { GetInternalStartServicesFn, InternalStartServices } from '../../../../types';
-import { FieldFormatsStart } from '../../../../field_formats';
-import { fieldFormatsServiceMock } from '../../../../field_formats/mocks';
+import { createFilterHistogram } from './histogram';
describe('AggConfig Filters', () => {
- let getInternalStartServices: GetInternalStartServicesFn;
- let fieldFormats: FieldFormatsStart;
-
- beforeEach(() => {
- fieldFormats = fieldFormatsServiceMock.createStartContract();
- getInternalStartServices = () => (({ fieldFormats } as unknown) as InternalStartServices);
- });
-
describe('histogram', () => {
const getConfig = (() => {}) as FieldFormatsGetConfigFn;
const getAggConfigs = () => {
@@ -72,12 +61,12 @@ describe('AggConfig Filters', () => {
test('should return an range filter for histogram', () => {
const aggConfigs = getAggConfigs();
- const filter = createFilterHistogram(getInternalStartServices)(
+ const filter = createFilterHistogram(mockGetFieldFormatsStart)(
aggConfigs.aggs[0] as IBucketAggConfig,
'2048'
);
- expect(fieldFormats.deserialize).toHaveBeenCalledTimes(1);
+ expect(mockGetFieldFormatsStart().deserialize).toHaveBeenCalledTimes(1);
expect(filter).toHaveProperty('meta');
expect(filter.meta).toHaveProperty('index', '1234');
expect(filter).toHaveProperty('range');
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.ts
similarity index 80%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/histogram.ts
index f3626bc9130ad..4684b1640cd82 100644
--- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts
+++ b/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.ts
@@ -17,14 +17,16 @@
* under the License.
*/
-import { IBucketAggConfig } from '../bucket_agg_type';
import { buildRangeFilter, RangeFilterParams } from '../../../../../common';
-import { GetInternalStartServicesFn } from '../../../../types';
+import { AggTypesDependencies } from '../../agg_types';
+import { IBucketAggConfig } from '../bucket_agg_type';
/** @internal */
-export const createFilterHistogram = (getInternalStartServices: GetInternalStartServicesFn) => {
+export const createFilterHistogram = (
+ getFieldFormatsStart: AggTypesDependencies['getFieldFormatsStart']
+) => {
return (aggConfig: IBucketAggConfig, key: string) => {
- const { fieldFormats } = getInternalStartServices();
+ const { deserialize } = getFieldFormatsStart();
const value = parseInt(key, 10);
const params: RangeFilterParams = { gte: value, lt: value + aggConfig.params.interval };
@@ -32,7 +34,7 @@ export const createFilterHistogram = (getInternalStartServices: GetInternalStart
aggConfig.params.field,
params,
aggConfig.getIndexPattern(),
- fieldFormats.deserialize(aggConfig.toSerializedFieldFormat()).convert(key)
+ deserialize(aggConfig.toSerializedFieldFormat()).convert(key)
);
};
};
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/ip_range.test.ts
similarity index 96%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/ip_range.test.ts
index 852685a505afd..9f823001aac8c 100644
--- a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/create_filter/ip_range.test.ts
@@ -17,7 +17,6 @@
* under the License.
*/
-import { getIpRangeBucketAgg } from '../ip_range';
import { createFilterIpRange } from './ip_range';
import { AggConfigs, CreateAggConfigParams } from '../../agg_configs';
import { mockAggTypesRegistry } from '../../test_helpers';
@@ -27,7 +26,7 @@ import { IBucketAggConfig } from '../bucket_agg_type';
describe('AggConfig Filters', () => {
describe('IP range', () => {
- const typesRegistry = mockAggTypesRegistry([getIpRangeBucketAgg()]);
+ const typesRegistry = mockAggTypesRegistry();
const getAggConfigs = (aggs: CreateAggConfigParams[]) => {
const field = {
name: 'ip',
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/ip_range.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/ip_range.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts
similarity index 73%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts
index faffad3beb8c1..30af970f55aa9 100644
--- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts
@@ -17,31 +17,15 @@
* under the License.
*/
-import { getRangeBucketAgg } from '../range';
-import { createFilterRange } from './range';
-import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common';
+import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common/field_formats';
import { AggConfigs } from '../../agg_configs';
-import { mockAggTypesRegistry } from '../../test_helpers';
-import { BUCKET_TYPES } from '../bucket_agg_types';
+import { mockAggTypesRegistry, mockGetFieldFormatsStart } from '../../test_helpers';
import { IBucketAggConfig } from '../bucket_agg_type';
-import { FieldFormatsStart } from '../../../../field_formats';
-import { fieldFormatsServiceMock } from '../../../../field_formats/mocks';
-import { GetInternalStartServicesFn, InternalStartServices } from '../../../../types';
+import { BUCKET_TYPES } from '../bucket_agg_types';
+import { createFilterRange } from './range';
describe('AggConfig Filters', () => {
describe('range', () => {
- let getInternalStartServices: GetInternalStartServicesFn;
- let fieldFormats: FieldFormatsStart;
-
- beforeEach(() => {
- fieldFormats = fieldFormatsServiceMock.createStartContract();
-
- getInternalStartServices = () =>
- (({
- fieldFormats,
- } as unknown) as InternalStartServices);
- });
-
const getConfig = (() => {}) as FieldFormatsGetConfigFn;
const getAggConfigs = () => {
const field = {
@@ -72,14 +56,14 @@ describe('AggConfig Filters', () => {
},
],
{
- typesRegistry: mockAggTypesRegistry([getRangeBucketAgg({ getInternalStartServices })]),
+ typesRegistry: mockAggTypesRegistry(),
}
);
};
test('should return a range filter for range agg', () => {
const aggConfigs = getAggConfigs();
- const filter = createFilterRange(getInternalStartServices)(
+ const filter = createFilterRange(mockGetFieldFormatsStart)(
aggConfigs.aggs[0] as IBucketAggConfig,
{
gte: 1024,
@@ -87,7 +71,7 @@ describe('AggConfig Filters', () => {
}
);
- expect(fieldFormats.deserialize).toHaveBeenCalledTimes(1);
+ expect(mockGetFieldFormatsStart().deserialize).toHaveBeenCalledTimes(1);
expect(filter).toHaveProperty('range');
expect(filter).toHaveProperty('meta');
expect(filter.meta).toHaveProperty('index', '1234');
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/range.ts
similarity index 78%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/range.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/range.ts
index f9db2973af136..8dea33a450c5d 100644
--- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts
+++ b/src/plugins/data/common/search/aggs/buckets/create_filter/range.ts
@@ -17,19 +17,21 @@
* under the License.
*/
-import { IBucketAggConfig } from '../bucket_agg_type';
import { buildRangeFilter } from '../../../../../common';
-import { GetInternalStartServicesFn } from '../../../../types';
+import { AggTypesDependencies } from '../../agg_types';
+import { IBucketAggConfig } from '../bucket_agg_type';
/** @internal */
-export const createFilterRange = (getInternalStartServices: GetInternalStartServicesFn) => {
+export const createFilterRange = (
+ getFieldFormatsStart: AggTypesDependencies['getFieldFormatsStart']
+) => {
return (aggConfig: IBucketAggConfig, params: any) => {
- const { fieldFormats } = getInternalStartServices();
+ const { deserialize } = getFieldFormatsStart();
return buildRangeFilter(
aggConfig.params.field,
params,
aggConfig.getIndexPattern(),
- fieldFormats.deserialize(aggConfig.toSerializedFieldFormat()).convert(params)
+ deserialize(aggConfig.toSerializedFieldFormat()).convert(params)
);
};
};
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/terms.test.ts
similarity index 97%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/terms.test.ts
index 1c165f0d29ab6..c3c661296e1cf 100644
--- a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/create_filter/terms.test.ts
@@ -17,7 +17,6 @@
* under the License.
*/
-import { getTermsBucketAgg } from '../terms';
import { createFilterTerms } from './terms';
import { AggConfigs, CreateAggConfigParams } from '../../agg_configs';
import { mockAggTypesRegistry } from '../../test_helpers';
@@ -43,7 +42,7 @@ describe('AggConfig Filters', () => {
};
return new AggConfigs(indexPattern, aggs, {
- typesRegistry: mockAggTypesRegistry([getTermsBucketAgg()]),
+ typesRegistry: mockAggTypesRegistry(),
});
};
diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/terms.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts
rename to src/plugins/data/common/search/aggs/buckets/create_filter/terms.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/common/search/aggs/buckets/date_histogram.ts
similarity index 92%
rename from src/plugins/data/public/search/aggs/buckets/date_histogram.ts
rename to src/plugins/data/common/search/aggs/buckets/date_histogram.ts
index fa1725eccbd28..fdf9c456b3876 100644
--- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts
+++ b/src/plugins/data/common/search/aggs/buckets/date_histogram.ts
@@ -20,27 +20,23 @@
import { get, noop, find, every } from 'lodash';
import moment from 'moment-timezone';
import { i18n } from '@kbn/i18n';
-import { IUiSettingsClient } from 'src/core/public';
-import { TimeBuckets } from './lib/time_buckets';
+import { KBN_FIELD_TYPES, TimeRange, TimeRangeBounds, UI_SETTINGS } from '../../../../common';
+
+import { intervalOptions } from './_interval_options';
+import { createFilterDateHistogram } from './create_filter/date_histogram';
import { BucketAggType, IBucketAggConfig } from './bucket_agg_type';
import { BUCKET_TYPES } from './bucket_agg_types';
-import { createFilterDateHistogram } from './create_filter/date_histogram';
-import { intervalOptions } from './_interval_options';
+import { ExtendedBounds } from './lib/extended_bounds';
+import { TimeBuckets } from './lib/time_buckets';
+
import { writeParams } from '../agg_params';
import { isMetricAggType } from '../metrics/metric_agg_type';
-
-import {
- dateHistogramInterval,
- KBN_FIELD_TYPES,
- TimeRange,
- TimeRangeBounds,
- UI_SETTINGS,
-} from '../../../../common';
import { BaseAggParams } from '../types';
-import { ExtendedBounds } from './lib/extended_bounds';
+import { dateHistogramInterval } from '../utils';
-type CalculateBoundsFn = (timeRange: TimeRange) => TimeRangeBounds;
+/** @internal */
+export type CalculateBoundsFn = (timeRange: TimeRange) => TimeRangeBounds;
const updateTimeBuckets = (
agg: IBucketDateHistogramAggConfig,
@@ -58,7 +54,8 @@ const updateTimeBuckets = (
export interface DateHistogramBucketAggDependencies {
calculateBounds: CalculateBoundsFn;
- uiSettings: IUiSettingsClient;
+ isDefaultTimezone: () => boolean;
+ getConfig: (key: string) => T;
}
export interface IBucketDateHistogramAggConfig extends IBucketAggConfig {
@@ -84,7 +81,8 @@ export interface AggParamsDateHistogram extends BaseAggParams {
export const getDateHistogramBucketAgg = ({
calculateBounds,
- uiSettings,
+ isDefaultTimezone,
+ getConfig,
}: DateHistogramBucketAggDependencies) =>
new BucketAggType({
name: BUCKET_TYPES.DATE_HISTOGRAM,
@@ -122,10 +120,10 @@ export const getDateHistogramBucketAgg = ({
if (buckets) return buckets;
buckets = new TimeBuckets({
- 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
- 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
- dateFormat: uiSettings.get('dateFormat'),
- 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
+ 'histogram:maxBars': getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS),
+ 'histogram:barTarget': getConfig(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
+ dateFormat: getConfig('dateFormat'),
+ 'dateFormat:scaled': getConfig('dateFormat:scaled'),
});
updateTimeBuckets(this, calculateBounds, buckets);
@@ -252,10 +250,9 @@ export const getDateHistogramBucketAgg = ({
}
if (!tz) {
// If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz
- const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz');
const detectedTimezone = moment.tz.guess();
const tzOffset = moment().format('Z');
- tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz');
+ tz = isDefaultTimezone() ? detectedTimezone || tzOffset : getConfig('dateFormat:tz');
}
output.params.time_zone = tz;
},
diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/date_histogram_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/date_histogram_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/date_histogram_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram_fn.ts b/src/plugins/data/common/search/aggs/buckets/date_histogram_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/date_histogram_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/date_histogram_fn.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts b/src/plugins/data/common/search/aggs/buckets/date_range.test.ts
similarity index 84%
rename from src/plugins/data/public/search/aggs/buckets/date_range.test.ts
rename to src/plugins/data/common/search/aggs/buckets/date_range.test.ts
index 69515dfee87fe..66f8e269cd38d 100644
--- a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/date_range.test.ts
@@ -17,19 +17,20 @@
* under the License.
*/
-import { coreMock } from '../../../../../../../src/core/public/mocks';
-import { getDateRangeBucketAgg, DateRangeBucketAggDependencies } from './date_range';
import { AggConfigs } from '../agg_configs';
-import { mockAggTypesRegistry } from '../test_helpers';
+import { AggTypesDependencies } from '../agg_types';
+import { mockAggTypesRegistry, mockAggTypesDependencies } from '../test_helpers';
import { BUCKET_TYPES } from './bucket_agg_types';
describe('date_range params', () => {
- let aggTypesDependencies: DateRangeBucketAggDependencies;
+ let aggTypesDependencies: AggTypesDependencies;
beforeEach(() => {
- const { uiSettings } = coreMock.createSetup();
-
- aggTypesDependencies = { uiSettings };
+ aggTypesDependencies = {
+ ...mockAggTypesDependencies,
+ getConfig: jest.fn(),
+ isDefaultTimezone: jest.fn().mockReturnValue(false),
+ };
});
const getAggConfigs = (params: Record = {}, hasIncludeTypeMeta: boolean = true) => {
@@ -68,7 +69,7 @@ describe('date_range params', () => {
},
],
{
- typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]),
+ typesRegistry: mockAggTypesRegistry(aggTypesDependencies),
}
);
};
@@ -108,10 +109,7 @@ describe('date_range params', () => {
test('should use the Kibana time_zone if no parameter specified', () => {
aggTypesDependencies = {
...aggTypesDependencies,
- uiSettings: {
- ...aggTypesDependencies.uiSettings,
- get: () => 'kibanaTimeZone' as any,
- },
+ getConfig: () => 'kibanaTimeZone' as any,
};
const aggConfigs = getAggConfigs(
diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.ts b/src/plugins/data/common/search/aggs/buckets/date_range.ts
similarity index 88%
rename from src/plugins/data/public/search/aggs/buckets/date_range.ts
rename to src/plugins/data/common/search/aggs/buckets/date_range.ts
index 8c576023f0239..eda35a77afa5f 100644
--- a/src/plugins/data/public/search/aggs/buckets/date_range.ts
+++ b/src/plugins/data/common/search/aggs/buckets/date_range.ts
@@ -20,14 +20,13 @@
import { get } from 'lodash';
import moment from 'moment-timezone';
import { i18n } from '@kbn/i18n';
-import { IUiSettingsClient } from 'src/core/public';
import { BUCKET_TYPES } from './bucket_agg_types';
import { BucketAggType, IBucketAggConfig } from './bucket_agg_type';
import { createFilterDateRange } from './create_filter/date_range';
import { DateRangeKey } from './lib/date_range';
-import { KBN_FIELD_TYPES } from '../../../../common';
+import { KBN_FIELD_TYPES } from '../../../../common/kbn_field_types/types';
import { BaseAggParams } from '../types';
const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', {
@@ -35,7 +34,8 @@ const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle',
});
export interface DateRangeBucketAggDependencies {
- uiSettings: IUiSettingsClient;
+ isDefaultTimezone: () => boolean;
+ getConfig: (key: string) => T;
}
export interface AggParamsDateRange extends BaseAggParams {
@@ -44,7 +44,10 @@ export interface AggParamsDateRange extends BaseAggParams {
time_zone?: string;
}
-export const getDateRangeBucketAgg = ({ uiSettings }: DateRangeBucketAggDependencies) =>
+export const getDateRangeBucketAgg = ({
+ isDefaultTimezone,
+ getConfig,
+}: DateRangeBucketAggDependencies) =>
new BucketAggType({
name: BUCKET_TYPES.DATE_RANGE,
title: dateRangeTitle,
@@ -100,9 +103,8 @@ export const getDateRangeBucketAgg = ({ uiSettings }: DateRangeBucketAggDependen
if (!tz) {
const detectedTimezone = moment.tz.guess();
const tzOffset = moment().format('Z');
- const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz');
- tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz');
+ tz = isDefaultTimezone() ? detectedTimezone || tzOffset : getConfig('dateFormat:tz');
}
output.params.time_zone = tz;
},
diff --git a/src/plugins/data/public/search/aggs/buckets/date_range_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/date_range_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/date_range_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/date_range_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/date_range_fn.ts b/src/plugins/data/common/search/aggs/buckets/date_range_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/date_range_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/date_range_fn.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/filter.ts b/src/plugins/data/common/search/aggs/buckets/filter.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/filter.ts
rename to src/plugins/data/common/search/aggs/buckets/filter.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/filter_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/filter_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/filter_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/filter_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/filter_fn.ts b/src/plugins/data/common/search/aggs/buckets/filter_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/filter_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/filter_fn.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/filters.test.ts b/src/plugins/data/common/search/aggs/buckets/filters.test.ts
similarity index 93%
rename from src/plugins/data/public/search/aggs/buckets/filters.test.ts
rename to src/plugins/data/common/search/aggs/buckets/filters.test.ts
index bcb82b5f99649..f745b4537131a 100644
--- a/src/plugins/data/public/search/aggs/buckets/filters.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/filters.test.ts
@@ -18,20 +18,20 @@
*/
import { Query } from '../../../../common';
-import { coreMock } from '../../../../../../../src/core/public/mocks';
import { AggConfigs } from '../agg_configs';
-import { mockAggTypesRegistry } from '../test_helpers';
+import { AggTypesDependencies } from '../agg_types';
+import { mockAggTypesRegistry, mockAggTypesDependencies } from '../test_helpers';
import { BUCKET_TYPES } from './bucket_agg_types';
-import { getFiltersBucketAgg, FiltersBucketAggDependencies } from './filters';
describe('Filters Agg', () => {
- let aggTypesDependencies: FiltersBucketAggDependencies;
+ let aggTypesDependencies: AggTypesDependencies;
beforeEach(() => {
jest.resetAllMocks();
- const { uiSettings } = coreMock.createSetup();
-
- aggTypesDependencies = { uiSettings };
+ aggTypesDependencies = {
+ ...mockAggTypesDependencies,
+ getConfig: jest.fn(),
+ };
});
describe('order agg editor UI', () => {
@@ -61,7 +61,7 @@ describe('Filters Agg', () => {
},
],
{
- typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]),
+ typesRegistry: mockAggTypesRegistry(aggTypesDependencies),
}
);
};
@@ -218,7 +218,7 @@ describe('Filters Agg', () => {
});
test('works with leading wildcards if allowed', () => {
- aggTypesDependencies.uiSettings.get = (s: any) =>
+ aggTypesDependencies.getConfig = (s: any) =>
s === 'query:allowLeadingWildcards' ? true : s;
const aggConfigs = getAggConfigs({
diff --git a/src/plugins/data/public/search/aggs/buckets/filters.ts b/src/plugins/data/common/search/aggs/buckets/filters.ts
similarity index 90%
rename from src/plugins/data/public/search/aggs/buckets/filters.ts
rename to src/plugins/data/common/search/aggs/buckets/filters.ts
index cd4ed721fda77..7310fa08b68e0 100644
--- a/src/plugins/data/public/search/aggs/buckets/filters.ts
+++ b/src/plugins/data/common/search/aggs/buckets/filters.ts
@@ -19,7 +19,6 @@
import { i18n } from '@kbn/i18n';
import { size, transform, cloneDeep } from 'lodash';
-import { IUiSettingsClient } from 'src/core/public';
import { createFilterFilters } from './create_filter/filters';
import { toAngularJSON } from '../utils';
@@ -41,7 +40,7 @@ interface FilterValue {
}
export interface FiltersBucketAggDependencies {
- uiSettings: IUiSettingsClient;
+ getConfig: (key: string) => any;
}
export interface AggParamsFilters extends Omit {
@@ -51,7 +50,7 @@ export interface AggParamsFilters extends Omit {
}>;
}
-export const getFiltersBucketAgg = ({ uiSettings }: FiltersBucketAggDependencies) =>
+export const getFiltersBucketAgg = ({ getConfig }: FiltersBucketAggDependencies) =>
new BucketAggType({
name: BUCKET_TYPES.FILTERS,
title: filtersTitle,
@@ -60,9 +59,9 @@ export const getFiltersBucketAgg = ({ uiSettings }: FiltersBucketAggDependencies
params: [
{
name: 'filters',
- default: [
+ default: () => [
{
- input: { query: '', language: uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE) },
+ input: { query: '', language: getConfig(UI_SETTINGS.SEARCH_QUERY_LANGUAGE) },
label: '',
},
],
@@ -80,7 +79,7 @@ export const getFiltersBucketAgg = ({ uiSettings }: FiltersBucketAggDependencies
return;
}
- const esQueryConfigs = getEsQueryConfig(uiSettings);
+ const esQueryConfigs = getEsQueryConfig({ get: getConfig });
const query = buildEsQuery(aggConfig.getIndexPattern(), [input], [], esQueryConfigs);
if (!query) {
diff --git a/src/plugins/data/public/search/aggs/buckets/filters_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/filters_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/filters_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/filters_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/filters_fn.ts b/src/plugins/data/common/search/aggs/buckets/filters_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/filters_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/filters_fn.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/plugins/data/common/search/aggs/buckets/geo_hash.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts
rename to src/plugins/data/common/search/aggs/buckets/geo_hash.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts b/src/plugins/data/common/search/aggs/buckets/geo_hash.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/geo_hash.ts
rename to src/plugins/data/common/search/aggs/buckets/geo_hash.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/geo_hash_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/geo_hash_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/geo_hash_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash_fn.ts b/src/plugins/data/common/search/aggs/buckets/geo_hash_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/geo_hash_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/geo_hash_fn.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/geo_tile.ts b/src/plugins/data/common/search/aggs/buckets/geo_tile.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/geo_tile.ts
rename to src/plugins/data/common/search/aggs/buckets/geo_tile.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/geo_tile_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/geo_tile_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/geo_tile_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/geo_tile_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/geo_tile_fn.ts b/src/plugins/data/common/search/aggs/buckets/geo_tile_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/geo_tile_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/geo_tile_fn.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/plugins/data/common/search/aggs/buckets/histogram.test.ts
similarity index 90%
rename from src/plugins/data/public/search/aggs/buckets/histogram.test.ts
rename to src/plugins/data/common/search/aggs/buckets/histogram.test.ts
index 6ac77f207d9ce..3727747984d3e 100644
--- a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/histogram.test.ts
@@ -17,33 +17,18 @@
* under the License.
*/
-import { coreMock } from '../../../../../../../src/core/public/mocks';
import { AggConfigs } from '../agg_configs';
-import { mockAggTypesRegistry } from '../test_helpers';
+import { mockAggTypesRegistry, mockAggTypesDependencies } from '../test_helpers';
+import { AggTypesDependencies } from '../agg_types';
import { BUCKET_TYPES } from './bucket_agg_types';
-import {
- IBucketHistogramAggConfig,
- getHistogramBucketAgg,
- AutoBounds,
- HistogramBucketAggDependencies,
-} from './histogram';
+import { IBucketHistogramAggConfig, getHistogramBucketAgg, AutoBounds } from './histogram';
import { BucketAggType } from './bucket_agg_type';
-import { fieldFormatsServiceMock } from '../../../field_formats/mocks';
-import { InternalStartServices } from '../../../types';
describe('Histogram Agg', () => {
- let aggTypesDependencies: HistogramBucketAggDependencies;
+ let aggTypesDependencies: AggTypesDependencies;
beforeEach(() => {
- const { uiSettings } = coreMock.createSetup();
-
- aggTypesDependencies = {
- uiSettings,
- getInternalStartServices: () =>
- (({
- fieldFormats: fieldFormatsServiceMock.createStartContract(),
- } as unknown) as InternalStartServices),
- };
+ aggTypesDependencies = { ...mockAggTypesDependencies };
});
const getAggConfigs = (params: Record) => {
@@ -72,7 +57,7 @@ describe('Histogram Agg', () => {
},
],
{
- typesRegistry: mockAggTypesRegistry([getHistogramBucketAgg(aggTypesDependencies)]),
+ typesRegistry: mockAggTypesRegistry(aggTypesDependencies),
}
);
};
@@ -167,10 +152,7 @@ describe('Histogram Agg', () => {
) => {
aggTypesDependencies = {
...aggTypesDependencies,
- uiSettings: {
- ...aggTypesDependencies.uiSettings,
- get: () => maxBars as any,
- },
+ getConfig: () => maxBars as any,
};
const aggConfigs = getAggConfigs({
diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.ts b/src/plugins/data/common/search/aggs/buckets/histogram.ts
similarity index 93%
rename from src/plugins/data/public/search/aggs/buckets/histogram.ts
rename to src/plugins/data/common/search/aggs/buckets/histogram.ts
index 500b6eab75d77..2b263013e55a2 100644
--- a/src/plugins/data/public/search/aggs/buckets/histogram.ts
+++ b/src/plugins/data/common/search/aggs/buckets/histogram.ts
@@ -19,14 +19,14 @@
import { get } from 'lodash';
import { i18n } from '@kbn/i18n';
-import { IUiSettingsClient } from 'src/core/public';
+
+import { KBN_FIELD_TYPES, UI_SETTINGS } from '../../../../common';
+import { AggTypesDependencies } from '../agg_types';
+import { BaseAggParams } from '../types';
import { BucketAggType, IBucketAggConfig } from './bucket_agg_type';
import { createFilterHistogram } from './create_filter/histogram';
import { BUCKET_TYPES } from './bucket_agg_types';
-import { KBN_FIELD_TYPES, UI_SETTINGS } from '../../../../common';
-import { GetInternalStartServicesFn } from '../../../types';
-import { BaseAggParams } from '../types';
import { ExtendedBounds } from './lib/extended_bounds';
export interface AutoBounds {
@@ -35,8 +35,8 @@ export interface AutoBounds {
}
export interface HistogramBucketAggDependencies {
- uiSettings: IUiSettingsClient;
- getInternalStartServices: GetInternalStartServicesFn;
+ getConfig: (key: string) => T;
+ getFieldFormatsStart: AggTypesDependencies['getFieldFormatsStart'];
}
export interface IBucketHistogramAggConfig extends IBucketAggConfig {
@@ -54,8 +54,8 @@ export interface AggParamsHistogram extends BaseAggParams {
}
export const getHistogramBucketAgg = ({
- uiSettings,
- getInternalStartServices,
+ getConfig,
+ getFieldFormatsStart,
}: HistogramBucketAggDependencies) =>
new BucketAggType({
name: BUCKET_TYPES.HISTOGRAM,
@@ -66,7 +66,7 @@ export const getHistogramBucketAgg = ({
makeLabel(aggConfig) {
return aggConfig.getFieldDisplayName();
},
- createFilter: createFilterHistogram(getInternalStartServices),
+ createFilter: createFilterHistogram(getFieldFormatsStart),
decorateAggConfig() {
let autoBounds: AutoBounds;
@@ -154,8 +154,8 @@ export const getHistogramBucketAgg = ({
const range = autoBounds.max - autoBounds.min;
const bars = range / interval;
- if (bars > uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS)) {
- const minInterval = range / uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS);
+ if (bars > getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS)) {
+ const minInterval = range / getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS);
// Round interval by order of magnitude to provide clean intervals
// Always round interval up so there will always be less buckets than histogram:maxBars
diff --git a/src/plugins/data/public/search/aggs/buckets/histogram_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/histogram_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/histogram_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/histogram_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/histogram_fn.ts b/src/plugins/data/common/search/aggs/buckets/histogram_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/histogram_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/histogram_fn.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/index.ts b/src/plugins/data/common/search/aggs/buckets/index.ts
similarity index 97%
rename from src/plugins/data/public/search/aggs/buckets/index.ts
rename to src/plugins/data/common/search/aggs/buckets/index.ts
index 7036cc7785db7..b16242e519872 100644
--- a/src/plugins/data/public/search/aggs/buckets/index.ts
+++ b/src/plugins/data/common/search/aggs/buckets/index.ts
@@ -18,6 +18,7 @@
*/
export * from './_interval_options';
+export * from './bucket_agg_type';
export * from './bucket_agg_types';
export * from './histogram';
export * from './date_histogram';
diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range.ts b/src/plugins/data/common/search/aggs/buckets/ip_range.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/ip_range.ts
rename to src/plugins/data/common/search/aggs/buckets/ip_range.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/ip_range_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/ip_range_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/ip_range_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range_fn.ts b/src/plugins/data/common/search/aggs/buckets/ip_range_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/ip_range_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/ip_range_fn.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/cidr_mask.test.ts b/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/lib/cidr_mask.test.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/cidr_mask.ts b/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.ts
similarity index 97%
rename from src/plugins/data/public/search/aggs/buckets/lib/cidr_mask.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.ts
index 57a7b378f305f..e4f6ab1e8da3c 100644
--- a/src/plugins/data/public/search/aggs/buckets/lib/cidr_mask.ts
+++ b/src/plugins/data/common/search/aggs/buckets/lib/cidr_mask.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { Ipv4Address } from '../../../../../common';
+import { Ipv4Address } from '../../utils';
const NUM_BITS = 32;
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/date_range.ts b/src/plugins/data/common/search/aggs/buckets/lib/date_range.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/lib/date_range.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/date_range.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/extended_bounds.ts b/src/plugins/data/common/search/aggs/buckets/lib/extended_bounds.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/lib/extended_bounds.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/extended_bounds.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/geo_point.ts b/src/plugins/data/common/search/aggs/buckets/lib/geo_point.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/lib/geo_point.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/geo_point.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/ip_range.ts b/src/plugins/data/common/search/aggs/buckets/lib/ip_range.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/lib/ip_range.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/ip_range.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/calc_auto_interval.test.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/lib/time_buckets/calc_auto_interval.test.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_auto_interval.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/calc_es_interval.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_es_interval.ts
similarity index 97%
rename from src/plugins/data/public/search/aggs/buckets/lib/time_buckets/calc_es_interval.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_es_interval.ts
index 3e7d315a0a42a..0ef2c571ca8fa 100644
--- a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/calc_es_interval.ts
+++ b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/calc_es_interval.ts
@@ -20,7 +20,7 @@
import moment from 'moment';
import dateMath, { Unit } from '@elastic/datemath';
-import { parseEsInterval } from '../../../../../../common';
+import { parseEsInterval } from '../../../utils';
const unitsDesc = dateMath.unitsDesc;
const largeMax = unitsDesc.indexOf('M');
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/index.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/index.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/lib/time_buckets/index.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/time_buckets/index.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.ts
similarity index 99%
rename from src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts
rename to src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.ts
index 017f646258c01..6402a6e83ead9 100644
--- a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts
+++ b/src/plugins/data/common/search/aggs/buckets/lib/time_buckets/time_buckets.ts
@@ -20,7 +20,7 @@
import { isString, isObject as isObjectLodash, isPlainObject, sortBy } from 'lodash';
import moment, { Moment } from 'moment';
-import { parseInterval } from '../../../../../../common';
+import { parseInterval } from '../../../utils';
import { TimeRangeBounds } from '../../../../../query';
import { calcAutoIntervalLessThan, calcAutoIntervalNear } from './calc_auto_interval';
import {
diff --git a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts b/src/plugins/data/common/search/aggs/buckets/migrate_include_exclude_format.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts
rename to src/plugins/data/common/search/aggs/buckets/migrate_include_exclude_format.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/range.test.ts b/src/plugins/data/common/search/aggs/buckets/range.test.ts
similarity index 79%
rename from src/plugins/data/public/search/aggs/buckets/range.test.ts
rename to src/plugins/data/common/search/aggs/buckets/range.test.ts
index f7c61a638158c..b23b03db6a9ec 100644
--- a/src/plugins/data/public/search/aggs/buckets/range.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/range.test.ts
@@ -17,26 +17,12 @@
* under the License.
*/
-import { getRangeBucketAgg, RangeBucketAggDependencies } from './range';
import { AggConfigs } from '../agg_configs';
import { mockAggTypesRegistry } from '../test_helpers';
import { BUCKET_TYPES } from './bucket_agg_types';
-import { FieldFormatsGetConfigFn, NumberFormat } from '../../../../common';
-import { fieldFormatsServiceMock } from '../../../field_formats/mocks';
-import { InternalStartServices } from '../../../types';
+import { FieldFormatsGetConfigFn, NumberFormat } from '../../../../common/field_formats';
describe('Range Agg', () => {
- let aggTypesDependencies: RangeBucketAggDependencies;
-
- beforeEach(() => {
- aggTypesDependencies = {
- getInternalStartServices: () =>
- (({
- fieldFormats: fieldFormatsServiceMock.createStartContract(),
- } as unknown) as InternalStartServices),
- };
- });
-
const getConfig = (() => {}) as FieldFormatsGetConfigFn;
const getAggConfigs = () => {
const field = {
@@ -74,7 +60,7 @@ describe('Range Agg', () => {
},
],
{
- typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]),
+ typesRegistry: mockAggTypesRegistry(),
}
);
};
diff --git a/src/plugins/data/public/search/aggs/buckets/range.ts b/src/plugins/data/common/search/aggs/buckets/range.ts
similarity index 90%
rename from src/plugins/data/public/search/aggs/buckets/range.ts
rename to src/plugins/data/common/search/aggs/buckets/range.ts
index 9f54f9fd0704e..91a357b635950 100644
--- a/src/plugins/data/public/search/aggs/buckets/range.ts
+++ b/src/plugins/data/common/search/aggs/buckets/range.ts
@@ -18,20 +18,22 @@
*/
import { i18n } from '@kbn/i18n';
-import { BucketAggType } from './bucket_agg_type';
+
import { KBN_FIELD_TYPES } from '../../../../common';
+import { AggTypesDependencies } from '../agg_types';
+import { BaseAggParams } from '../types';
+
+import { BucketAggType } from './bucket_agg_type';
import { RangeKey } from './range_key';
import { createFilterRange } from './create_filter/range';
import { BUCKET_TYPES } from './bucket_agg_types';
-import { GetInternalStartServicesFn } from '../../../types';
-import { BaseAggParams } from '../types';
const rangeTitle = i18n.translate('data.search.aggs.buckets.rangeTitle', {
defaultMessage: 'Range',
});
export interface RangeBucketAggDependencies {
- getInternalStartServices: GetInternalStartServicesFn;
+ getFieldFormatsStart: AggTypesDependencies['getFieldFormatsStart'];
}
export interface AggParamsRange extends BaseAggParams {
@@ -42,13 +44,13 @@ export interface AggParamsRange extends BaseAggParams {
}>;
}
-export const getRangeBucketAgg = ({ getInternalStartServices }: RangeBucketAggDependencies) => {
+export const getRangeBucketAgg = ({ getFieldFormatsStart }: RangeBucketAggDependencies) => {
const keyCaches = new WeakMap();
return new BucketAggType({
name: BUCKET_TYPES.RANGE,
title: rangeTitle,
- createFilter: createFilterRange(getInternalStartServices),
+ createFilter: createFilterRange(getFieldFormatsStart),
makeLabel(aggConfig) {
return i18n.translate('data.search.aggs.aggTypesLabel', {
defaultMessage: '{fieldName} ranges',
diff --git a/src/plugins/data/public/search/aggs/buckets/range_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/range_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/range_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/range_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/range_fn.ts b/src/plugins/data/common/search/aggs/buckets/range_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/range_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/range_fn.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/range_key.ts b/src/plugins/data/common/search/aggs/buckets/range_key.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/range_key.ts
rename to src/plugins/data/common/search/aggs/buckets/range_key.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/plugins/data/common/search/aggs/buckets/significant_terms.test.ts
similarity index 95%
rename from src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts
rename to src/plugins/data/common/search/aggs/buckets/significant_terms.test.ts
index f13fafc2b17e6..e6c7bbee72a72 100644
--- a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/significant_terms.test.ts
@@ -20,7 +20,6 @@
import { AggConfigs, IAggConfigs } from '../agg_configs';
import { mockAggTypesRegistry } from '../test_helpers';
import { BUCKET_TYPES } from './bucket_agg_types';
-import { getSignificantTermsBucketAgg } from './significant_terms';
describe('Significant Terms Agg', () => {
describe('order agg editor UI', () => {
@@ -51,7 +50,7 @@ describe('Significant Terms Agg', () => {
},
],
{
- typesRegistry: mockAggTypesRegistry([getSignificantTermsBucketAgg()]),
+ typesRegistry: mockAggTypesRegistry(),
}
);
};
diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.ts b/src/plugins/data/common/search/aggs/buckets/significant_terms.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/significant_terms.ts
rename to src/plugins/data/common/search/aggs/buckets/significant_terms.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/significant_terms_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/significant_terms_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/significant_terms_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms_fn.ts b/src/plugins/data/common/search/aggs/buckets/significant_terms_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/significant_terms_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/significant_terms_fn.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/terms.test.ts b/src/plugins/data/common/search/aggs/buckets/terms.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/terms.test.ts
rename to src/plugins/data/common/search/aggs/buckets/terms.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/common/search/aggs/buckets/terms.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/terms.ts
rename to src/plugins/data/common/search/aggs/buckets/terms.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/terms_fn.test.ts b/src/plugins/data/common/search/aggs/buckets/terms_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/terms_fn.test.ts
rename to src/plugins/data/common/search/aggs/buckets/terms_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/buckets/terms_fn.ts b/src/plugins/data/common/search/aggs/buckets/terms_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/buckets/terms_fn.ts
rename to src/plugins/data/common/search/aggs/buckets/terms_fn.ts
diff --git a/src/plugins/data/public/search/aggs/index.test.ts b/src/plugins/data/common/search/aggs/index.test.ts
similarity index 63%
rename from src/plugins/data/public/search/aggs/index.test.ts
rename to src/plugins/data/common/search/aggs/index.test.ts
index 73068326ca97e..1fdc28d1c7839 100644
--- a/src/plugins/data/public/search/aggs/index.test.ts
+++ b/src/plugins/data/common/search/aggs/index.test.ts
@@ -17,42 +17,34 @@
* under the License.
*/
-import { coreMock } from '../../../../../../src/core/public/mocks';
import { getAggTypes } from './index';
+import { mockGetFieldFormatsStart } from './test_helpers';
import { isBucketAggType } from './buckets/bucket_agg_type';
import { isMetricAggType } from './metrics/metric_agg_type';
-import { FieldFormatsStart } from '../../field_formats';
-import { InternalStartServices } from '../../types';
describe('AggTypesComponent', () => {
- const coreSetup = coreMock.createSetup();
- const coreStart = coreMock.createSetup();
-
- const aggTypes = getAggTypes({
- calculateBounds: jest.fn(),
- getInternalStartServices: () =>
- (({
- notifications: coreStart.notifications,
- fieldFormats: {} as FieldFormatsStart,
- } as unknown) as InternalStartServices),
- uiSettings: coreSetup.uiSettings,
- });
-
+ const aggTypes = getAggTypes();
const { buckets, metrics } = aggTypes;
+ const aggTypesDependencies = {
+ calculateBounds: jest.fn(),
+ getConfig: jest.fn(),
+ getFieldFormatsStart: mockGetFieldFormatsStart,
+ isDefaultTimezone: jest.fn().mockReturnValue(true),
+ };
describe('bucket aggs', () => {
test('all extend BucketAggType', () => {
- buckets.forEach((bucketAgg) => {
- expect(isBucketAggType(bucketAgg)).toBeTruthy();
+ buckets.forEach(({ fn }) => {
+ expect(isBucketAggType(fn(aggTypesDependencies))).toBeTruthy();
});
});
});
describe('metric aggs', () => {
test('all extend MetricAggType', () => {
- metrics.forEach((metricAgg) => {
- expect(isMetricAggType(metricAgg)).toBeTruthy();
+ metrics.forEach(({ fn }) => {
+ expect(isMetricAggType(fn(aggTypesDependencies))).toBeTruthy();
});
});
});
diff --git a/src/plugins/data/common/search/aggs/index.ts b/src/plugins/data/common/search/aggs/index.ts
index 7a5b7d509c940..1c4ae27e49530 100644
--- a/src/plugins/data/common/search/aggs/index.ts
+++ b/src/plugins/data/common/search/aggs/index.ts
@@ -17,5 +17,15 @@
* under the License.
*/
-export * from './date_interval_utils';
-export * from './ipv4_address';
+export * from './agg_config';
+export * from './agg_configs';
+export * from './agg_groups';
+export * from './agg_type';
+export * from './agg_types';
+export * from './agg_types_registry';
+export * from './aggs_service';
+export * from './buckets';
+export * from './metrics';
+export * from './param_types';
+export * from './types';
+export * from './utils';
diff --git a/src/plugins/data/public/search/aggs/metrics/avg.ts b/src/plugins/data/common/search/aggs/metrics/avg.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/avg.ts
rename to src/plugins/data/common/search/aggs/metrics/avg.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/avg_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/avg_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/avg_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/avg_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/avg_fn.ts b/src/plugins/data/common/search/aggs/metrics/avg_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/avg_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/avg_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts b/src/plugins/data/common/search/aggs/metrics/bucket_avg.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_avg.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_avg.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_avg_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/bucket_avg_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_avg_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_avg_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_avg_fn.ts b/src/plugins/data/common/search/aggs/metrics/bucket_avg_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_avg_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_avg_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts b/src/plugins/data/common/search/aggs/metrics/bucket_max.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_max.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_max.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_max_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/bucket_max_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_max_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_max_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_max_fn.ts b/src/plugins/data/common/search/aggs/metrics/bucket_max_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_max_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_max_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts b/src/plugins/data/common/search/aggs/metrics/bucket_min.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_min.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_min.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_min_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/bucket_min_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_min_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_min_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_min_fn.ts b/src/plugins/data/common/search/aggs/metrics/bucket_min_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_min_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_min_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts b/src/plugins/data/common/search/aggs/metrics/bucket_sum.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_sum.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_sum.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_sum_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/bucket_sum_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_sum_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_sum_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_sum_fn.ts b/src/plugins/data/common/search/aggs/metrics/bucket_sum_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/bucket_sum_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/bucket_sum_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/cardinality.ts b/src/plugins/data/common/search/aggs/metrics/cardinality.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/cardinality.ts
rename to src/plugins/data/common/search/aggs/metrics/cardinality.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/cardinality_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/cardinality_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/cardinality_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/cardinality_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/cardinality_fn.ts b/src/plugins/data/common/search/aggs/metrics/cardinality_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/cardinality_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/cardinality_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/count.ts b/src/plugins/data/common/search/aggs/metrics/count.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/count.ts
rename to src/plugins/data/common/search/aggs/metrics/count.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/count_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/count_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/count_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/count_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/count_fn.ts b/src/plugins/data/common/search/aggs/metrics/count_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/count_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/count_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts b/src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts
rename to src/plugins/data/common/search/aggs/metrics/cumulative_sum.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/cumulative_sum_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/cumulative_sum_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/cumulative_sum_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/cumulative_sum_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/cumulative_sum_fn.ts b/src/plugins/data/common/search/aggs/metrics/cumulative_sum_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/cumulative_sum_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/cumulative_sum_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/derivative.ts b/src/plugins/data/common/search/aggs/metrics/derivative.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/derivative.ts
rename to src/plugins/data/common/search/aggs/metrics/derivative.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/derivative_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/derivative_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/derivative_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/derivative_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/derivative_fn.ts b/src/plugins/data/common/search/aggs/metrics/derivative_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/derivative_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/derivative_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts b/src/plugins/data/common/search/aggs/metrics/geo_bounds.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/geo_bounds.ts
rename to src/plugins/data/common/search/aggs/metrics/geo_bounds.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/geo_bounds_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/geo_bounds_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/geo_bounds_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/geo_bounds_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/geo_bounds_fn.ts b/src/plugins/data/common/search/aggs/metrics/geo_bounds_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/geo_bounds_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/geo_bounds_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts b/src/plugins/data/common/search/aggs/metrics/geo_centroid.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/geo_centroid.ts
rename to src/plugins/data/common/search/aggs/metrics/geo_centroid.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/geo_centroid_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/geo_centroid_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/geo_centroid_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/geo_centroid_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/geo_centroid_fn.ts b/src/plugins/data/common/search/aggs/metrics/geo_centroid_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/geo_centroid_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/geo_centroid_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/index.ts b/src/plugins/data/common/search/aggs/metrics/index.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/index.ts
rename to src/plugins/data/common/search/aggs/metrics/index.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts b/src/plugins/data/common/search/aggs/metrics/lib/get_response_agg_config_class.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts
rename to src/plugins/data/common/search/aggs/metrics/lib/get_response_agg_config_class.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/make_nested_label.test.ts b/src/plugins/data/common/search/aggs/metrics/lib/make_nested_label.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/lib/make_nested_label.test.ts
rename to src/plugins/data/common/search/aggs/metrics/lib/make_nested_label.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/make_nested_label.ts b/src/plugins/data/common/search/aggs/metrics/lib/make_nested_label.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/lib/make_nested_label.ts
rename to src/plugins/data/common/search/aggs/metrics/lib/make_nested_label.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/nested_agg_helpers.ts b/src/plugins/data/common/search/aggs/metrics/lib/nested_agg_helpers.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/lib/nested_agg_helpers.ts
rename to src/plugins/data/common/search/aggs/metrics/lib/nested_agg_helpers.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/ordinal_suffix.test.ts b/src/plugins/data/common/search/aggs/metrics/lib/ordinal_suffix.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/lib/ordinal_suffix.test.ts
rename to src/plugins/data/common/search/aggs/metrics/lib/ordinal_suffix.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/ordinal_suffix.ts b/src/plugins/data/common/search/aggs/metrics/lib/ordinal_suffix.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/lib/ordinal_suffix.ts
rename to src/plugins/data/common/search/aggs/metrics/lib/ordinal_suffix.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts b/src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts
rename to src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_writer.ts b/src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_writer.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_writer.ts
rename to src/plugins/data/common/search/aggs/metrics/lib/parent_pipeline_agg_writer.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts b/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts
rename to src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_writer.ts b/src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_writer.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_writer.ts
rename to src/plugins/data/common/search/aggs/metrics/lib/sibling_pipeline_agg_writer.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/max.ts b/src/plugins/data/common/search/aggs/metrics/max.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/max.ts
rename to src/plugins/data/common/search/aggs/metrics/max.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/max_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/max_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/max_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/max_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/max_fn.ts b/src/plugins/data/common/search/aggs/metrics/max_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/max_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/max_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/median.test.ts b/src/plugins/data/common/search/aggs/metrics/median.test.ts
similarity index 94%
rename from src/plugins/data/public/search/aggs/metrics/median.test.ts
rename to src/plugins/data/common/search/aggs/metrics/median.test.ts
index 22d907330e2a3..f3f2d157ebafc 100644
--- a/src/plugins/data/public/search/aggs/metrics/median.test.ts
+++ b/src/plugins/data/common/search/aggs/metrics/median.test.ts
@@ -17,7 +17,6 @@
* under the License.
*/
-import { getMedianMetricAgg } from './median';
import { AggConfigs, IAggConfigs } from '../agg_configs';
import { mockAggTypesRegistry } from '../test_helpers';
import { METRIC_TYPES } from './metric_agg_types';
@@ -26,7 +25,7 @@ describe('AggTypeMetricMedianProvider class', () => {
let aggConfigs: IAggConfigs;
beforeEach(() => {
- const typesRegistry = mockAggTypesRegistry([getMedianMetricAgg()]);
+ const typesRegistry = mockAggTypesRegistry();
const field = {
name: 'bytes',
};
diff --git a/src/plugins/data/public/search/aggs/metrics/median.ts b/src/plugins/data/common/search/aggs/metrics/median.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/median.ts
rename to src/plugins/data/common/search/aggs/metrics/median.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/median_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/median_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/median_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/median_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/median_fn.ts b/src/plugins/data/common/search/aggs/metrics/median_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/median_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/median_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts b/src/plugins/data/common/search/aggs/metrics/metric_agg_type.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts
rename to src/plugins/data/common/search/aggs/metrics/metric_agg_type.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/metric_agg_types.ts b/src/plugins/data/common/search/aggs/metrics/metric_agg_types.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/metric_agg_types.ts
rename to src/plugins/data/common/search/aggs/metrics/metric_agg_types.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/min.ts b/src/plugins/data/common/search/aggs/metrics/min.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/min.ts
rename to src/plugins/data/common/search/aggs/metrics/min.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/min_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/min_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/min_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/min_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/min_fn.ts b/src/plugins/data/common/search/aggs/metrics/min_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/min_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/min_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts b/src/plugins/data/common/search/aggs/metrics/moving_avg.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/moving_avg.ts
rename to src/plugins/data/common/search/aggs/metrics/moving_avg.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/moving_avg_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/moving_avg_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/moving_avg_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/moving_avg_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/moving_avg_fn.ts b/src/plugins/data/common/search/aggs/metrics/moving_avg_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/moving_avg_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/moving_avg_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/plugins/data/common/search/aggs/metrics/parent_pipeline.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts
rename to src/plugins/data/common/search/aggs/metrics/parent_pipeline.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/plugins/data/common/search/aggs/metrics/percentile_ranks.test.ts
similarity index 77%
rename from src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts
rename to src/plugins/data/common/search/aggs/metrics/percentile_ranks.test.ts
index 348aecc23243a..970daf5b62458 100644
--- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts
+++ b/src/plugins/data/common/search/aggs/metrics/percentile_ranks.test.ts
@@ -23,29 +23,16 @@ import {
PercentileRanksMetricAggDependencies,
} from './percentile_ranks';
import { AggConfigs, IAggConfigs } from '../agg_configs';
-import { mockAggTypesRegistry } from '../test_helpers';
+import { mockAggTypesRegistry, mockGetFieldFormatsStart } from '../test_helpers';
import { METRIC_TYPES } from './metric_agg_types';
-import { FieldFormatsStart } from '../../../field_formats';
-import { fieldFormatsServiceMock } from '../../../field_formats/mocks';
-import { InternalStartServices } from '../../../types';
describe('AggTypesMetricsPercentileRanksProvider class', function () {
let aggConfigs: IAggConfigs;
- let fieldFormats: FieldFormatsStart;
let aggTypesDependencies: PercentileRanksMetricAggDependencies;
beforeEach(() => {
- fieldFormats = fieldFormatsServiceMock.createStartContract();
- fieldFormats.getDefaultInstance = (() => ({
- convert: (t?: string) => t,
- })) as any;
- aggTypesDependencies = {
- getInternalStartServices: () =>
- (({
- fieldFormats,
- } as unknown) as InternalStartServices),
- };
- const typesRegistry = mockAggTypesRegistry([getPercentileRanksMetricAgg(aggTypesDependencies)]);
+ aggTypesDependencies = { getFieldFormatsStart: mockGetFieldFormatsStart };
+ const typesRegistry = mockAggTypesRegistry();
const field = {
name: 'bytes',
};
diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts b/src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts
similarity index 86%
rename from src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts
rename to src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts
index 3c0be229f1bbd..664cc1ad02ada 100644
--- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts
+++ b/src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts
@@ -18,13 +18,15 @@
*/
import { i18n } from '@kbn/i18n';
+
+import { KBN_FIELD_TYPES } from '../../../../common';
+import { AggTypesDependencies } from '../agg_types';
+import { BaseAggParams } from '../types';
+
import { MetricAggType } from './metric_agg_type';
import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class';
import { getPercentileValue } from './percentiles_get_value';
import { METRIC_TYPES } from './metric_agg_types';
-import { KBN_FIELD_TYPES } from '../../../../common';
-import { GetInternalStartServicesFn } from '../../../types';
-import { BaseAggParams } from '../types';
export interface AggParamsPercentileRanks extends BaseAggParams {
field: string;
@@ -35,16 +37,17 @@ export interface AggParamsPercentileRanks extends BaseAggParams {
export type IPercentileRanksAggConfig = IResponseAggConfig;
export interface PercentileRanksMetricAggDependencies {
- getInternalStartServices: GetInternalStartServicesFn;
+ getFieldFormatsStart: AggTypesDependencies['getFieldFormatsStart'];
}
-const getValueProps = (getInternalStartServices: GetInternalStartServicesFn) => {
+const getValueProps = (
+ getFieldFormatsStart: PercentileRanksMetricAggDependencies['getFieldFormatsStart']
+) => {
return {
makeLabel(this: IPercentileRanksAggConfig) {
- const { fieldFormats } = getInternalStartServices();
+ const { getDefaultInstance } = getFieldFormatsStart();
const field = this.getField();
- const format =
- (field && field.format) || fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER);
+ const format = (field && field.format) || getDefaultInstance(KBN_FIELD_TYPES.NUMBER);
const customLabel = this.getParam('customLabel');
const label = customLabel || this.getFieldDisplayName();
@@ -57,7 +60,7 @@ const getValueProps = (getInternalStartServices: GetInternalStartServicesFn) =>
};
export const getPercentileRanksMetricAgg = ({
- getInternalStartServices,
+ getFieldFormatsStart,
}: PercentileRanksMetricAggDependencies) => {
return new MetricAggType({
name: METRIC_TYPES.PERCENTILE_RANKS,
@@ -87,10 +90,7 @@ export const getPercentileRanksMetricAgg = ({
},
],
getResponseAggs(agg) {
- const ValueAggConfig = getResponseAggConfigClass(
- agg,
- getValueProps(getInternalStartServices)
- );
+ const ValueAggConfig = getResponseAggConfigClass(agg, getValueProps(getFieldFormatsStart));
const values = agg.getParam('values');
return values.map((value: any) => new ValueAggConfig(value));
diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/percentile_ranks_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/percentile_ranks_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/percentile_ranks_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks_fn.ts b/src/plugins/data/common/search/aggs/metrics/percentile_ranks_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/percentile_ranks_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/percentile_ranks_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/plugins/data/common/search/aggs/metrics/percentiles.test.ts
similarity index 96%
rename from src/plugins/data/public/search/aggs/metrics/percentiles.test.ts
rename to src/plugins/data/common/search/aggs/metrics/percentiles.test.ts
index a44c0e5075ef9..10e98df5a4eeb 100644
--- a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts
+++ b/src/plugins/data/common/search/aggs/metrics/percentiles.test.ts
@@ -26,7 +26,7 @@ describe('AggTypesMetricsPercentilesProvider class', () => {
let aggConfigs: IAggConfigs;
beforeEach(() => {
- const typesRegistry = mockAggTypesRegistry([getPercentilesMetricAgg()]);
+ const typesRegistry = mockAggTypesRegistry();
const field = {
name: 'bytes',
};
diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.ts b/src/plugins/data/common/search/aggs/metrics/percentiles.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/percentiles.ts
rename to src/plugins/data/common/search/aggs/metrics/percentiles.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/percentiles_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/percentiles_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/percentiles_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles_fn.ts b/src/plugins/data/common/search/aggs/metrics/percentiles_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/percentiles_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/percentiles_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles_get_value.ts b/src/plugins/data/common/search/aggs/metrics/percentiles_get_value.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/percentiles_get_value.ts
rename to src/plugins/data/common/search/aggs/metrics/percentiles_get_value.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts b/src/plugins/data/common/search/aggs/metrics/serial_diff.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/serial_diff.ts
rename to src/plugins/data/common/search/aggs/metrics/serial_diff.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/serial_diff_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/serial_diff_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/serial_diff_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/serial_diff_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/serial_diff_fn.ts b/src/plugins/data/common/search/aggs/metrics/serial_diff_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/serial_diff_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/serial_diff_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/plugins/data/common/search/aggs/metrics/sibling_pipeline.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts
rename to src/plugins/data/common/search/aggs/metrics/sibling_pipeline.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts b/src/plugins/data/common/search/aggs/metrics/std_deviation.test.ts
similarity index 97%
rename from src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts
rename to src/plugins/data/common/search/aggs/metrics/std_deviation.test.ts
index c3efe95f44a56..f2f30fcde42eb 100644
--- a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts
+++ b/src/plugins/data/common/search/aggs/metrics/std_deviation.test.ts
@@ -23,7 +23,7 @@ import { mockAggTypesRegistry } from '../test_helpers';
import { METRIC_TYPES } from './metric_agg_types';
describe('AggTypeMetricStandardDeviationProvider class', () => {
- const typesRegistry = mockAggTypesRegistry([getStdDeviationMetricAgg()]);
+ const typesRegistry = mockAggTypesRegistry();
const getAggConfigs = (customLabel?: string) => {
const field = {
name: 'memory',
diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.ts b/src/plugins/data/common/search/aggs/metrics/std_deviation.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/std_deviation.ts
rename to src/plugins/data/common/search/aggs/metrics/std_deviation.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/std_deviation_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/std_deviation_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/std_deviation_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation_fn.ts b/src/plugins/data/common/search/aggs/metrics/std_deviation_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/std_deviation_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/std_deviation_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/sum.ts b/src/plugins/data/common/search/aggs/metrics/sum.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/sum.ts
rename to src/plugins/data/common/search/aggs/metrics/sum.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/sum_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/sum_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/sum_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/sum_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/sum_fn.ts b/src/plugins/data/common/search/aggs/metrics/sum_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/sum_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/sum_fn.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/plugins/data/common/search/aggs/metrics/top_hit.test.ts
similarity index 99%
rename from src/plugins/data/public/search/aggs/metrics/top_hit.test.ts
rename to src/plugins/data/common/search/aggs/metrics/top_hit.test.ts
index c2434df3ae53c..c0cbfb33c842b 100644
--- a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts
+++ b/src/plugins/data/common/search/aggs/metrics/top_hit.test.ts
@@ -36,7 +36,7 @@ describe('Top hit metric', () => {
fieldType = KBN_FIELD_TYPES.NUMBER,
size = 1,
}: any) => {
- const typesRegistry = mockAggTypesRegistry([getTopHitMetricAgg()]);
+ const typesRegistry = mockAggTypesRegistry();
const field = {
name: fieldName,
displayName: fieldName,
diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.ts b/src/plugins/data/common/search/aggs/metrics/top_hit.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/top_hit.ts
rename to src/plugins/data/common/search/aggs/metrics/top_hit.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/top_hit_fn.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/top_hit_fn.test.ts
rename to src/plugins/data/common/search/aggs/metrics/top_hit_fn.test.ts
diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit_fn.ts b/src/plugins/data/common/search/aggs/metrics/top_hit_fn.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/metrics/top_hit_fn.ts
rename to src/plugins/data/common/search/aggs/metrics/top_hit_fn.ts
diff --git a/src/plugins/data/public/search/aggs/param_types/agg.ts b/src/plugins/data/common/search/aggs/param_types/agg.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/param_types/agg.ts
rename to src/plugins/data/common/search/aggs/param_types/agg.ts
diff --git a/src/plugins/data/public/search/aggs/param_types/base.ts b/src/plugins/data/common/search/aggs/param_types/base.ts
similarity index 96%
rename from src/plugins/data/public/search/aggs/param_types/base.ts
rename to src/plugins/data/common/search/aggs/param_types/base.ts
index 1ba8a75e98cbe..3a12a9a54500f 100644
--- a/src/plugins/data/public/search/aggs/param_types/base.ts
+++ b/src/plugins/data/common/search/aggs/param_types/base.ts
@@ -17,11 +17,10 @@
* under the License.
*/
+import { FetchOptions, ISearchSource } from 'src/plugins/data/public';
import { ExpressionAstFunction } from 'src/plugins/expressions/common';
import { IAggConfigs } from '../agg_configs';
import { IAggConfig } from '../agg_config';
-import { FetchOptions } from '../../fetch';
-import { ISearchSource } from '../../search_source';
export class BaseParamType {
name: string;
diff --git a/src/plugins/data/public/search/aggs/param_types/field.test.ts b/src/plugins/data/common/search/aggs/param_types/field.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/param_types/field.test.ts
rename to src/plugins/data/common/search/aggs/param_types/field.test.ts
diff --git a/src/plugins/data/public/search/aggs/param_types/field.ts b/src/plugins/data/common/search/aggs/param_types/field.ts
similarity index 96%
rename from src/plugins/data/public/search/aggs/param_types/field.ts
rename to src/plugins/data/common/search/aggs/param_types/field.ts
index 7c00bc668a39f..492294bdf4e5f 100644
--- a/src/plugins/data/public/search/aggs/param_types/field.ts
+++ b/src/plugins/data/common/search/aggs/param_types/field.ts
@@ -22,8 +22,8 @@ import { IAggConfig } from '../agg_config';
import { SavedObjectNotFound } from '../../../../../../plugins/kibana_utils/common';
import { BaseParamType } from './base';
import { propFilter } from '../utils';
-import { isNestedField, KBN_FIELD_TYPES } from '../../../../common';
-import { IndexPatternField } from '../../../index_patterns';
+import { KBN_FIELD_TYPES } from '../../../kbn_field_types/types';
+import { isNestedField, IndexPatternField } from '../../../index_patterns/fields';
const filterByType = propFilter('type');
diff --git a/src/plugins/data/public/search/aggs/param_types/index.ts b/src/plugins/data/common/search/aggs/param_types/index.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/param_types/index.ts
rename to src/plugins/data/common/search/aggs/param_types/index.ts
diff --git a/src/plugins/data/public/search/aggs/param_types/json.test.ts b/src/plugins/data/common/search/aggs/param_types/json.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/param_types/json.test.ts
rename to src/plugins/data/common/search/aggs/param_types/json.test.ts
diff --git a/src/plugins/data/public/search/aggs/param_types/json.ts b/src/plugins/data/common/search/aggs/param_types/json.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/param_types/json.ts
rename to src/plugins/data/common/search/aggs/param_types/json.ts
diff --git a/src/plugins/data/public/search/aggs/param_types/optioned.test.ts b/src/plugins/data/common/search/aggs/param_types/optioned.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/param_types/optioned.test.ts
rename to src/plugins/data/common/search/aggs/param_types/optioned.test.ts
diff --git a/src/plugins/data/public/search/aggs/param_types/optioned.ts b/src/plugins/data/common/search/aggs/param_types/optioned.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/param_types/optioned.ts
rename to src/plugins/data/common/search/aggs/param_types/optioned.ts
diff --git a/src/plugins/data/public/search/aggs/param_types/string.test.ts b/src/plugins/data/common/search/aggs/param_types/string.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/param_types/string.test.ts
rename to src/plugins/data/common/search/aggs/param_types/string.test.ts
diff --git a/src/plugins/data/public/search/aggs/param_types/string.ts b/src/plugins/data/common/search/aggs/param_types/string.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/param_types/string.ts
rename to src/plugins/data/common/search/aggs/param_types/string.ts
diff --git a/src/plugins/data/public/search/aggs/test_helpers/function_wrapper.ts b/src/plugins/data/common/search/aggs/test_helpers/function_wrapper.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/test_helpers/function_wrapper.ts
rename to src/plugins/data/common/search/aggs/test_helpers/function_wrapper.ts
diff --git a/src/plugins/data/public/search/aggs/test_helpers/index.ts b/src/plugins/data/common/search/aggs/test_helpers/index.ts
similarity index 86%
rename from src/plugins/data/public/search/aggs/test_helpers/index.ts
rename to src/plugins/data/common/search/aggs/test_helpers/index.ts
index d47317d8b4725..30f315f276741 100644
--- a/src/plugins/data/public/search/aggs/test_helpers/index.ts
+++ b/src/plugins/data/common/search/aggs/test_helpers/index.ts
@@ -17,5 +17,5 @@
* under the License.
*/
-export { functionWrapper } from './function_wrapper';
-export { mockAggTypesRegistry } from './mock_agg_types_registry';
+export * from './function_wrapper';
+export * from './mock_agg_types_registry';
diff --git a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/common/search/aggs/test_helpers/mock_agg_types_registry.ts
similarity index 52%
rename from src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts
rename to src/plugins/data/common/search/aggs/test_helpers/mock_agg_types_registry.ts
index 4a0820c349b5f..14631a9d53055 100644
--- a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts
+++ b/src/plugins/data/common/search/aggs/test_helpers/mock_agg_types_registry.ts
@@ -17,17 +17,14 @@
* under the License.
*/
-import { coreMock } from '../../../../../../../src/core/public/mocks';
+import { fieldFormatsMock } from '../../../field_formats/mocks';
+
import { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry';
-import { getAggTypes } from '../agg_types';
-import { BucketAggType } from '../buckets/bucket_agg_type';
-import { MetricAggType } from '../metrics/metric_agg_type';
-import { fieldFormatsServiceMock } from '../../../field_formats/mocks';
-import { InternalStartServices } from '../../../types';
+import { AggTypesDependencies, getAggTypes } from '../agg_types';
import { TimeBucketsConfig } from '../buckets/lib/time_buckets/time_buckets';
// Mocked uiSettings shared among aggs unit tests
-const mockUiSettings = jest.fn().mockImplementation((key: string) => {
+const mockGetConfig = jest.fn().mockImplementation((key: string) => {
const config: TimeBucketsConfig = {
'histogram:maxBars': 4,
'histogram:barTarget': 3,
@@ -44,6 +41,23 @@ const mockUiSettings = jest.fn().mockImplementation((key: string) => {
return config[key] ?? key;
});
+/** @internal */
+export function mockGetFieldFormatsStart() {
+ const { deserialize, getDefaultInstance } = fieldFormatsMock;
+ return {
+ deserialize,
+ getDefaultInstance,
+ };
+}
+
+/** @internal */
+export const mockAggTypesDependencies: AggTypesDependencies = {
+ calculateBounds: jest.fn(),
+ getFieldFormatsStart: mockGetFieldFormatsStart,
+ getConfig: mockGetConfig,
+ isDefaultTimezone: () => true,
+};
+
/**
* Testing utility which creates a new instance of AggTypesRegistry,
* registers the provided agg types, and returns AggTypesRegistry.start()
@@ -56,36 +70,37 @@ const mockUiSettings = jest.fn().mockImplementation((key: string) => {
*
* @internal
*/
-export function mockAggTypesRegistry | MetricAggType>(
- types?: T[]
-): AggTypesRegistryStart {
+export function mockAggTypesRegistry(deps?: AggTypesDependencies): AggTypesRegistryStart {
const registry = new AggTypesRegistry();
+ const initializedAggTypes = new Map();
const registrySetup = registry.setup();
- if (types) {
- types.forEach((type) => {
- if (type instanceof BucketAggType) {
- registrySetup.registerBucket(type);
- } else if (type instanceof MetricAggType) {
- registrySetup.registerMetric(type);
- }
- });
- } else {
- const coreSetup = coreMock.createSetup();
- coreSetup.uiSettings.get = mockUiSettings;
+ const aggTypes = getAggTypes();
+
+ aggTypes.buckets.forEach(({ name, fn }) => registrySetup.registerBucket(name, fn));
+ aggTypes.metrics.forEach(({ name, fn }) => registrySetup.registerMetric(name, fn));
- const aggTypes = getAggTypes({
- calculateBounds: jest.fn(),
- getInternalStartServices: () =>
- (({
- fieldFormats: fieldFormatsServiceMock.createStartContract(),
- } as unknown) as InternalStartServices),
- uiSettings: coreSetup.uiSettings,
- });
+ const registryStart = registry.start();
- aggTypes.buckets.forEach((type) => registrySetup.registerBucket(type));
- aggTypes.metrics.forEach((type) => registrySetup.registerMetric(type));
- }
+ // initialize each agg type and store in memory
+ registryStart.getAll().buckets.forEach((type) => {
+ const agg = type(deps ?? mockAggTypesDependencies);
+ initializedAggTypes.set(agg.name, agg);
+ });
+ registryStart.getAll().metrics.forEach((type) => {
+ const agg = type(deps ?? mockAggTypesDependencies);
+ initializedAggTypes.set(agg.name, agg);
+ });
- return registry.start();
+ return {
+ get: (name: string) => {
+ return initializedAggTypes.get(name);
+ },
+ getAll: () => {
+ return {
+ buckets: Array.from(initializedAggTypes.values()).filter((agg) => agg.type === 'buckets'),
+ metrics: Array.from(initializedAggTypes.values()).filter((agg) => agg.type === 'metrics'),
+ };
+ },
+ };
}
diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts
new file mode 100644
index 0000000000000..dabd653463d4f
--- /dev/null
+++ b/src/plugins/data/common/search/aggs/types.ts
@@ -0,0 +1,157 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Assign } from '@kbn/utility-types';
+import { IndexPattern } from '../../index_patterns/index_patterns/index_pattern';
+import {
+ AggConfigSerialized,
+ AggConfigs,
+ AggParamsRange,
+ AggParamsIpRange,
+ AggParamsDateRange,
+ AggParamsFilter,
+ AggParamsFilters,
+ AggParamsSignificantTerms,
+ AggParamsGeoTile,
+ AggParamsGeoHash,
+ AggParamsTerms,
+ AggParamsAvg,
+ AggParamsCardinality,
+ AggParamsGeoBounds,
+ AggParamsGeoCentroid,
+ AggParamsMax,
+ AggParamsMedian,
+ AggParamsMin,
+ AggParamsStdDeviation,
+ AggParamsSum,
+ AggParamsBucketAvg,
+ AggParamsBucketMax,
+ AggParamsBucketMin,
+ AggParamsBucketSum,
+ AggParamsCumulativeSum,
+ AggParamsDerivative,
+ AggParamsMovingAvg,
+ AggParamsPercentileRanks,
+ AggParamsPercentiles,
+ AggParamsSerialDiff,
+ AggParamsTopHit,
+ AggParamsHistogram,
+ AggParamsDateHistogram,
+ AggTypesRegistry,
+ AggTypesRegistrySetup,
+ AggTypesRegistryStart,
+ CreateAggConfigParams,
+ getCalculateAutoTimeExpression,
+ METRIC_TYPES,
+ BUCKET_TYPES,
+} from './';
+
+export { IAggConfig, AggConfigSerialized } from './agg_config';
+export { CreateAggConfigParams, IAggConfigs } from './agg_configs';
+export { IAggType } from './agg_type';
+export { AggParam, AggParamOption } from './agg_params';
+export { IFieldParamType } from './param_types';
+export { IMetricAggType } from './metrics/metric_agg_type';
+export { DateRangeKey } from './buckets/lib/date_range';
+export { IpRangeKey } from './buckets/lib/ip_range';
+export { OptionedValueProp } from './param_types/optioned';
+
+/** @internal */
+export interface AggsCommonSetup {
+ types: AggTypesRegistrySetup;
+}
+
+/** @internal */
+export interface AggsCommonStart {
+ calculateAutoTimeExpression: ReturnType;
+ createAggConfigs: (
+ indexPattern: IndexPattern,
+ configStates?: CreateAggConfigParams[],
+ schemas?: Record
+ ) => InstanceType;
+ types: ReturnType;
+}
+
+/**
+ * AggsStart represents the actual external contract as AggsCommonStart
+ * is only used internally. The difference is that AggsStart includes the
+ * typings for the registry with initialized agg types.
+ *
+ * @internal
+ */
+export type AggsStart = Assign;
+
+/** @internal */
+export interface BaseAggParams {
+ json?: string;
+ customLabel?: string;
+}
+
+/** @internal */
+export interface AggExpressionType {
+ type: 'agg_type';
+ value: AggConfigSerialized;
+}
+
+/** @internal */
+export type AggExpressionFunctionArgs<
+ Name extends keyof AggParamsMapping
+> = AggParamsMapping[Name] & Pick;
+
+/**
+ * A global list of the param interfaces for each agg type.
+ * For now this is internal, but eventually we will probably
+ * want to make it public.
+ *
+ * @internal
+ */
+export interface AggParamsMapping {
+ [BUCKET_TYPES.RANGE]: AggParamsRange;
+ [BUCKET_TYPES.IP_RANGE]: AggParamsIpRange;
+ [BUCKET_TYPES.DATE_RANGE]: AggParamsDateRange;
+ [BUCKET_TYPES.FILTER]: AggParamsFilter;
+ [BUCKET_TYPES.FILTERS]: AggParamsFilters;
+ [BUCKET_TYPES.SIGNIFICANT_TERMS]: AggParamsSignificantTerms;
+ [BUCKET_TYPES.GEOTILE_GRID]: AggParamsGeoTile;
+ [BUCKET_TYPES.GEOHASH_GRID]: AggParamsGeoHash;
+ [BUCKET_TYPES.HISTOGRAM]: AggParamsHistogram;
+ [BUCKET_TYPES.DATE_HISTOGRAM]: AggParamsDateHistogram;
+ [BUCKET_TYPES.TERMS]: AggParamsTerms;
+ [METRIC_TYPES.AVG]: AggParamsAvg;
+ [METRIC_TYPES.CARDINALITY]: AggParamsCardinality;
+ [METRIC_TYPES.COUNT]: BaseAggParams;
+ [METRIC_TYPES.GEO_BOUNDS]: AggParamsGeoBounds;
+ [METRIC_TYPES.GEO_CENTROID]: AggParamsGeoCentroid;
+ [METRIC_TYPES.MAX]: AggParamsMax;
+ [METRIC_TYPES.MEDIAN]: AggParamsMedian;
+ [METRIC_TYPES.MIN]: AggParamsMin;
+ [METRIC_TYPES.STD_DEV]: AggParamsStdDeviation;
+ [METRIC_TYPES.SUM]: AggParamsSum;
+ [METRIC_TYPES.AVG_BUCKET]: AggParamsBucketAvg;
+ [METRIC_TYPES.MAX_BUCKET]: AggParamsBucketMax;
+ [METRIC_TYPES.MIN_BUCKET]: AggParamsBucketMin;
+ [METRIC_TYPES.SUM_BUCKET]: AggParamsBucketSum;
+ [METRIC_TYPES.CUMULATIVE_SUM]: AggParamsCumulativeSum;
+ [METRIC_TYPES.DERIVATIVE]: AggParamsDerivative;
+ [METRIC_TYPES.MOVING_FN]: AggParamsMovingAvg;
+ [METRIC_TYPES.PERCENTILE_RANKS]: AggParamsPercentileRanks;
+ [METRIC_TYPES.PERCENTILES]: AggParamsPercentiles;
+ [METRIC_TYPES.SERIAL_DIFF]: AggParamsSerialDiff;
+ [METRIC_TYPES.TOP_HITS]: AggParamsTopHit;
+}
diff --git a/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts b/src/plugins/data/common/search/aggs/utils/calculate_auto_time_expression.ts
similarity index 71%
rename from src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts
rename to src/plugins/data/common/search/aggs/utils/calculate_auto_time_expression.ts
index 30fcdd9d83a38..622e8101f34ab 100644
--- a/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts
+++ b/src/plugins/data/common/search/aggs/utils/calculate_auto_time_expression.ts
@@ -17,11 +17,12 @@
* under the License.
*/
import moment from 'moment';
-import { IUiSettingsClient } from 'src/core/public';
+import { UI_SETTINGS } from '../../../../common/constants';
+import { TimeRange } from '../../../../common/query';
import { TimeBuckets } from '../buckets/lib/time_buckets';
-import { toAbsoluteDates, TimeRange, UI_SETTINGS } from '../../../../common';
+import { toAbsoluteDates } from './date_interval_utils';
-export function getCalculateAutoTimeExpression(uiSettings: IUiSettingsClient) {
+export function getCalculateAutoTimeExpression(getConfig: (key: string) => any) {
return function calculateAutoTimeExpression(range: TimeRange) {
const dates = toAbsoluteDates(range);
if (!dates) {
@@ -29,10 +30,10 @@ export function getCalculateAutoTimeExpression(uiSettings: IUiSettingsClient) {
}
const buckets = new TimeBuckets({
- 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
- 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
- dateFormat: uiSettings.get('dateFormat'),
- 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
+ 'histogram:maxBars': getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS),
+ 'histogram:barTarget': getConfig(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
+ dateFormat: getConfig('dateFormat'),
+ 'dateFormat:scaled': getConfig('dateFormat:scaled'),
});
buckets.setInterval('auto');
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/date_histogram_interval.test.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/date_histogram_interval.test.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/date_histogram_interval.test.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/date_histogram_interval.test.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/date_histogram_interval.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/date_histogram_interval.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/date_histogram_interval.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/date_histogram_interval.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/index.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/index.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/index.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/index.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/invalid_es_calendar_interval_error.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/invalid_es_calendar_interval_error.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/invalid_es_calendar_interval_error.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/invalid_es_calendar_interval_error.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/invalid_es_interval_format_error.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/invalid_es_interval_format_error.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/invalid_es_interval_format_error.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/invalid_es_interval_format_error.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/is_valid_es_interval.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/is_valid_es_interval.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/is_valid_es_interval.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/is_valid_es_interval.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/is_valid_interval.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/is_valid_interval.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/is_valid_interval.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/is_valid_interval.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/least_common_interval.test.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/least_common_interval.test.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/least_common_interval.test.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/least_common_interval.test.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/least_common_interval.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/least_common_interval.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/least_common_interval.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/least_common_interval.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/least_common_multiple.test.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/least_common_multiple.test.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/least_common_multiple.test.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/least_common_multiple.test.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/least_common_multiple.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/least_common_multiple.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/least_common_multiple.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/least_common_multiple.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.test.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_es_interval.test.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.test.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_es_interval.test.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_es_interval.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/parse_es_interval.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_es_interval.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.test.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_interval.test.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.test.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_interval.test.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_interval.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/date_interval_utils/parse_interval.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/parse_interval.ts
diff --git a/src/plugins/data/common/search/aggs/date_interval_utils/to_absolute_dates.ts b/src/plugins/data/common/search/aggs/utils/date_interval_utils/to_absolute_dates.ts
similarity index 95%
rename from src/plugins/data/common/search/aggs/date_interval_utils/to_absolute_dates.ts
rename to src/plugins/data/common/search/aggs/utils/date_interval_utils/to_absolute_dates.ts
index 98d752a72e28a..99809e06df38f 100644
--- a/src/plugins/data/common/search/aggs/date_interval_utils/to_absolute_dates.ts
+++ b/src/plugins/data/common/search/aggs/utils/date_interval_utils/to_absolute_dates.ts
@@ -18,7 +18,7 @@
*/
import dateMath from '@elastic/datemath';
-import { TimeRange } from '../../../../common';
+import { TimeRange } from '../../../../../common';
export function toAbsoluteDates(range: TimeRange) {
const fromDate = dateMath.parse(range.from);
diff --git a/src/plugins/data/public/search/aggs/utils/get_format_with_aggs.test.ts b/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.test.ts
similarity index 95%
rename from src/plugins/data/public/search/aggs/utils/get_format_with_aggs.test.ts
rename to src/plugins/data/common/search/aggs/utils/get_format_with_aggs.test.ts
index 3b440bc50c93b..20d8cfc105e49 100644
--- a/src/plugins/data/public/search/aggs/utils/get_format_with_aggs.test.ts
+++ b/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.test.ts
@@ -19,9 +19,8 @@
import { identity } from 'lodash';
-import { SerializedFieldFormat } from '../../../../../expressions/common/types';
-import { FieldFormat } from '../../../../common';
-import { IFieldFormat } from '../../../../public';
+import { SerializedFieldFormat } from 'src/plugins/expressions/common/types';
+import { FieldFormat, IFieldFormat } from '../../../../common';
import { getFormatWithAggs } from './get_format_with_aggs';
diff --git a/src/plugins/data/public/search/aggs/utils/get_format_with_aggs.ts b/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.ts
similarity index 95%
rename from src/plugins/data/public/search/aggs/utils/get_format_with_aggs.ts
rename to src/plugins/data/common/search/aggs/utils/get_format_with_aggs.ts
index e0db249c7cf86..01369206ab3cf 100644
--- a/src/plugins/data/public/search/aggs/utils/get_format_with_aggs.ts
+++ b/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.ts
@@ -19,9 +19,12 @@
import { i18n } from '@kbn/i18n';
-import { SerializedFieldFormat } from '../../../../../expressions/common/types';
-import { FieldFormat } from '../../../../common';
-import { FieldFormatsContentType, IFieldFormat } from '../../../../public';
+import { SerializedFieldFormat } from 'src/plugins/expressions/common/types';
+import {
+ FieldFormat,
+ FieldFormatsContentType,
+ IFieldFormat,
+} from '../../../../common/field_formats';
import { convertDateRangeToString, DateRangeKey } from '../buckets/lib/date_range';
import { convertIPRangeToString, IpRangeKey } from '../buckets/lib/ip_range';
diff --git a/src/plugins/data/public/search/aggs/utils/get_parsed_value.ts b/src/plugins/data/common/search/aggs/utils/get_parsed_value.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/utils/get_parsed_value.ts
rename to src/plugins/data/common/search/aggs/utils/get_parsed_value.ts
diff --git a/src/plugins/data/public/search/aggs/utils/index.ts b/src/plugins/data/common/search/aggs/utils/index.ts
similarity index 93%
rename from src/plugins/data/public/search/aggs/utils/index.ts
rename to src/plugins/data/common/search/aggs/utils/index.ts
index 5a889ee9ead9d..99ce44207d80d 100644
--- a/src/plugins/data/public/search/aggs/utils/index.ts
+++ b/src/plugins/data/common/search/aggs/utils/index.ts
@@ -18,6 +18,8 @@
*/
export * from './calculate_auto_time_expression';
+export * from './date_interval_utils';
export * from './get_format_with_aggs';
+export * from './ipv4_address';
export * from './prop_filter';
export * from './to_angular_json';
diff --git a/src/plugins/data/common/search/aggs/ipv4_address.test.ts b/src/plugins/data/common/search/aggs/utils/ipv4_address.test.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/ipv4_address.test.ts
rename to src/plugins/data/common/search/aggs/utils/ipv4_address.test.ts
diff --git a/src/plugins/data/common/search/aggs/ipv4_address.ts b/src/plugins/data/common/search/aggs/utils/ipv4_address.ts
similarity index 100%
rename from src/plugins/data/common/search/aggs/ipv4_address.ts
rename to src/plugins/data/common/search/aggs/utils/ipv4_address.ts
diff --git a/src/plugins/data/public/search/aggs/utils/prop_filter.test.ts b/src/plugins/data/common/search/aggs/utils/prop_filter.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/utils/prop_filter.test.ts
rename to src/plugins/data/common/search/aggs/utils/prop_filter.test.ts
diff --git a/src/plugins/data/public/search/aggs/utils/prop_filter.ts b/src/plugins/data/common/search/aggs/utils/prop_filter.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/utils/prop_filter.ts
rename to src/plugins/data/common/search/aggs/utils/prop_filter.ts
diff --git a/src/plugins/data/public/search/aggs/utils/to_angular_json.ts b/src/plugins/data/common/search/aggs/utils/to_angular_json.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/utils/to_angular_json.ts
rename to src/plugins/data/common/search/aggs/utils/to_angular_json.ts
diff --git a/src/plugins/data/common/search/expressions/index.ts b/src/plugins/data/common/search/expressions/index.ts
index f1a39a8383629..25839a805d8c5 100644
--- a/src/plugins/data/common/search/expressions/index.ts
+++ b/src/plugins/data/common/search/expressions/index.ts
@@ -18,3 +18,4 @@
*/
export * from './esaggs';
+export * from './utils';
diff --git a/src/plugins/data/public/search/expressions/utils/courier_inspector_stats.ts b/src/plugins/data/common/search/expressions/utils/courier_inspector_stats.ts
similarity index 98%
rename from src/plugins/data/public/search/expressions/utils/courier_inspector_stats.ts
rename to src/plugins/data/common/search/expressions/utils/courier_inspector_stats.ts
index c933e8cd3e961..d41f02d2728d5 100644
--- a/src/plugins/data/public/search/expressions/utils/courier_inspector_stats.ts
+++ b/src/plugins/data/common/search/expressions/utils/courier_inspector_stats.ts
@@ -26,8 +26,8 @@
import { i18n } from '@kbn/i18n';
import { SearchResponse } from 'elasticsearch';
+import { ISearchSource } from 'src/plugins/data/public';
import { RequestStatistics } from 'src/plugins/inspector/common';
-import { ISearchSource } from '../../search_source';
/** @public */
export function getRequestInspectorStats(searchSource: ISearchSource) {
diff --git a/src/plugins/data/common/search/expressions/utils/index.ts b/src/plugins/data/common/search/expressions/utils/index.ts
new file mode 100644
index 0000000000000..75c1809770c78
--- /dev/null
+++ b/src/plugins/data/common/search/expressions/utils/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export * from './courier_inspector_stats';
diff --git a/src/plugins/data/common/search/index.ts b/src/plugins/data/common/search/index.ts
index 45daf16d10c6d..557ab64079d16 100644
--- a/src/plugins/data/common/search/index.ts
+++ b/src/plugins/data/common/search/index.ts
@@ -17,10 +17,13 @@
* under the License.
*/
-import { ES_SEARCH_STRATEGY } from './es_search';
-
-export { IKibanaSearchResponse, IKibanaSearchRequest } from './types';
+export * from './aggs';
+export * from './es_search';
+export * from './expressions';
+export * from './tabify';
+export * from './types';
+import { ES_SEARCH_STRATEGY } from './es_search';
export const DEFAULT_SEARCH_STRATEGY = ES_SEARCH_STRATEGY;
export {
diff --git a/src/plugins/data/public/search/tabify/buckets.test.ts b/src/plugins/data/common/search/tabify/buckets.test.ts
similarity index 100%
rename from src/plugins/data/public/search/tabify/buckets.test.ts
rename to src/plugins/data/common/search/tabify/buckets.test.ts
diff --git a/src/plugins/data/public/search/tabify/buckets.ts b/src/plugins/data/common/search/tabify/buckets.ts
similarity index 100%
rename from src/plugins/data/public/search/tabify/buckets.ts
rename to src/plugins/data/common/search/tabify/buckets.ts
diff --git a/src/plugins/data/public/search/tabify/get_columns.test.ts b/src/plugins/data/common/search/tabify/get_columns.test.ts
similarity index 100%
rename from src/plugins/data/public/search/tabify/get_columns.test.ts
rename to src/plugins/data/common/search/tabify/get_columns.test.ts
diff --git a/src/plugins/data/public/search/tabify/get_columns.ts b/src/plugins/data/common/search/tabify/get_columns.ts
similarity index 100%
rename from src/plugins/data/public/search/tabify/get_columns.ts
rename to src/plugins/data/common/search/tabify/get_columns.ts
diff --git a/src/plugins/data/public/search/tabify/index.ts b/src/plugins/data/common/search/tabify/index.ts
similarity index 100%
rename from src/plugins/data/public/search/tabify/index.ts
rename to src/plugins/data/common/search/tabify/index.ts
diff --git a/src/plugins/data/public/search/tabify/response_writer.test.ts b/src/plugins/data/common/search/tabify/response_writer.test.ts
similarity index 100%
rename from src/plugins/data/public/search/tabify/response_writer.test.ts
rename to src/plugins/data/common/search/tabify/response_writer.test.ts
diff --git a/src/plugins/data/public/search/tabify/response_writer.ts b/src/plugins/data/common/search/tabify/response_writer.ts
similarity index 100%
rename from src/plugins/data/public/search/tabify/response_writer.ts
rename to src/plugins/data/common/search/tabify/response_writer.ts
diff --git a/src/plugins/data/public/search/tabify/tabify.test.ts b/src/plugins/data/common/search/tabify/tabify.test.ts
similarity index 98%
rename from src/plugins/data/public/search/tabify/tabify.test.ts
rename to src/plugins/data/common/search/tabify/tabify.test.ts
index 0a1e99c8bb200..6b9d520b11436 100644
--- a/src/plugins/data/public/search/tabify/tabify.test.ts
+++ b/src/plugins/data/common/search/tabify/tabify.test.ts
@@ -18,7 +18,7 @@
*/
import { tabifyAggResponse } from './tabify';
-import { IndexPattern } from '../../index_patterns';
+import { IndexPattern } from '../../index_patterns/index_patterns/index_pattern';
import { AggConfigs, IAggConfig, IAggConfigs } from '../aggs';
import { mockAggTypesRegistry } from '../aggs/test_helpers';
import { metricOnly, threeTermBuckets } from 'fixtures/fake_hierarchical_data';
diff --git a/src/plugins/data/public/search/tabify/tabify.ts b/src/plugins/data/common/search/tabify/tabify.ts
similarity index 100%
rename from src/plugins/data/public/search/tabify/tabify.ts
rename to src/plugins/data/common/search/tabify/tabify.ts
diff --git a/src/plugins/data/public/search/tabify/types.ts b/src/plugins/data/common/search/tabify/types.ts
similarity index 100%
rename from src/plugins/data/public/search/tabify/types.ts
rename to src/plugins/data/common/search/tabify/types.ts
diff --git a/src/plugins/data/public/field_formats/utils/deserialize.ts b/src/plugins/data/public/field_formats/utils/deserialize.ts
index 26baa5fdeb1e4..7595a443bf8f0 100644
--- a/src/plugins/data/public/field_formats/utils/deserialize.ts
+++ b/src/plugins/data/public/field_formats/utils/deserialize.ts
@@ -23,9 +23,9 @@ import { SerializedFieldFormat } from '../../../../expressions/common/types';
import { FieldFormat } from '../../../common';
import { FormatFactory } from '../../../common/field_formats/utils';
+import { getFormatWithAggs } from '../../../common/search/aggs';
import { DataPublicPluginStart, IFieldFormat } from '../../../public';
import { getUiSettings } from '../../../public/services';
-import { getFormatWithAggs } from '../../search/aggs/utils';
const getConfig = (key: string, defaultOverride?: any): any =>
getUiSettings().get(key, defaultOverride);
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index f036d5f30a0e2..ecf076aa517fb 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -294,15 +294,6 @@ import {
propFilter,
siblingPipelineType,
termsAggFilter,
- // expressions utils
- getRequestInspectorStats,
- getResponseInspectorStats,
- // tabify
- tabifyAggResponse,
- tabifyGetColumns,
-} from './search';
-
-import {
dateHistogramInterval,
InvalidEsCalendarIntervalError,
InvalidEsIntervalFormatError,
@@ -312,10 +303,14 @@ import {
parseEsInterval,
parseInterval,
toAbsoluteDates,
+ // expressions utils
+ getRequestInspectorStats,
+ getResponseInspectorStats,
+ // tabify
+ tabifyAggResponse,
+ tabifyGetColumns,
} from '../common';
-export { EsaggsExpressionFunctionDefinition, ParsedInterval } from '../common';
-
export {
// aggs
AggGroupLabels,
@@ -326,6 +321,7 @@ export {
AggParamType,
AggConfigOptions,
BUCKET_TYPES,
+ EsaggsExpressionFunctionDefinition,
IAggConfig,
IAggConfigs,
IAggType,
@@ -334,35 +330,42 @@ export {
METRIC_TYPES,
OptionedParamType,
OptionedValueProp,
+ ParsedInterval,
+ // tabify
+ TabbedAggColumn,
+ TabbedAggRow,
+ TabbedTable,
+} from '../common';
+
+export {
// search
ES_SEARCH_STRATEGY,
+ EsQuerySortValue,
+ extractSearchSourceReferences,
+ FetchOptions,
getEsPreference,
- ISearch,
- ISearchOptions,
- ISearchGeneric,
- IEsSearchResponse,
+ getSearchParamsFromRequest,
IEsSearchRequest,
- IKibanaSearchResponse,
+ IEsSearchResponse,
IKibanaSearchRequest,
- SearchRequest,
- SearchResponse,
- SearchError,
+ IKibanaSearchResponse,
+ injectSearchSourceReferences,
+ ISearch,
+ ISearchGeneric,
+ ISearchOptions,
ISearchSource,
parseSearchSourceJSON,
- injectSearchSourceReferences,
- getSearchParamsFromRequest,
- extractSearchSourceReferences,
- SearchSourceFields,
- EsQuerySortValue,
- SortDirection,
- FetchOptions,
- // tabify
- TabbedAggColumn,
- TabbedAggRow,
- TabbedTable,
+ RequestTimeoutError,
+ SearchError,
SearchInterceptor,
SearchInterceptorDeps,
- RequestTimeoutError,
+ SearchRequest,
+ SearchResponse,
+ SearchSourceFields,
+ SortDirection,
+ // expression functions and types
+ EsdslExpressionFunctionDefinition,
+ EsRawResponseExpressionTypeDefinition,
} from './search';
// Search namespace
diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts
index 3fc1e6454829d..78e40cfedd906 100644
--- a/src/plugins/data/public/mocks.ts
+++ b/src/plugins/data/public/mocks.ts
@@ -79,7 +79,7 @@ const createStartContract = (): Start => {
};
export { createSearchSourceMock } from './search/mocks';
-export { getCalculateAutoTimeExpression } from './search/aggs';
+export { getCalculateAutoTimeExpression } from '../common/search/aggs';
export const dataPluginMock = {
createSetupContract,
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index e950434b287a7..564c571b6ccd6 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -33,7 +33,6 @@ import {
DataPublicPluginStart,
DataSetupDependencies,
DataStartDependencies,
- InternalStartServices,
DataPublicPluginEnhancements,
} from './types';
import { AutocompleteService } from './autocomplete';
@@ -120,17 +119,6 @@ export class DataPublicPlugin
): DataPublicPluginSetup {
const startServices = createStartServicesGetter(core.getStartServices);
- const getInternalStartServices = (): InternalStartServices => {
- const { core: coreStart, self } = startServices();
- return {
- fieldFormats: self.fieldFormats,
- notifications: coreStart.notifications,
- uiSettings: coreStart.uiSettings,
- searchService: self.search,
- injectedMetadata: coreStart.injectedMetadata,
- };
- };
-
expressions.registerFunction(esaggs);
expressions.registerFunction(indexPatternLoad);
@@ -158,10 +146,9 @@ export class DataPublicPlugin
);
const searchService = this.searchService.setup(core, {
- expressions,
usageCollection,
- getInternalStartServices,
packageInfo: this.packageInfo,
+ expressions,
});
return {
@@ -210,7 +197,7 @@ export class DataPublicPlugin
});
setQueryService(query);
- const search = this.searchService.start(core, { indexPatterns });
+ const search = this.searchService.start(core, { fieldFormats, indexPatterns });
setSearchService(search);
uiActions.addTriggerAction(
@@ -247,5 +234,7 @@ export class DataPublicPlugin
public stop() {
this.autocomplete.clearProviders();
+ this.queryService.stop();
+ this.searchService.stop();
}
}
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index a61334905e9f5..58c2bd9957ab8 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -26,10 +26,12 @@ import { EuiGlobalToastListToast } from '@elastic/eui';
import { ExclusiveUnion } from '@elastic/eui';
import { ExpressionAstFunction } from 'src/plugins/expressions/common';
import { ExpressionsSetup } from 'src/plugins/expressions/public';
+import { FetchOptions as FetchOptions_2 } from 'src/plugins/data/public';
import { History } from 'history';
import { Href } from 'history';
import { IconType } from '@elastic/eui';
import { InjectedIntl } from '@kbn/i18n/react';
+import { ISearchSource as ISearchSource_2 } from 'src/plugins/data/public';
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { IUiSettingsClient } from 'src/core/public';
import { IUiSettingsClient as IUiSettingsClient_3 } from 'kibana/public';
@@ -153,12 +155,12 @@ export interface ApplyGlobalFilterActionContext {
timeFieldName?: string;
}
-// Warning: (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "DateFormat" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "baseFormattersPublic" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
-export const baseFormattersPublic: (import("../../common").FieldFormatInstanceType | typeof DateNanosFormat | typeof DateFormat)[];
+export const baseFormattersPublic: (import("../../common").FieldFormatInstanceType | typeof DateFormat | typeof DateNanosFormat)[];
// Warning: (ae-missing-release-tag) "BUCKET_TYPES" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -346,6 +348,15 @@ export const ES_SEARCH_STRATEGY = "es";
// @public (undocumented)
export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'esaggs', Input, Arguments, Output>;
+// Warning: (ae-forgotten-export) The symbol "name" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "Input" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "Arguments" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "Output" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "EsdslExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition;
+
// Warning: (ae-missing-release-tag) "esFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -442,6 +453,14 @@ export interface EsQueryConfig {
// @public (undocumented)
export type EsQuerySortValue = Record;
+// Warning: (ae-forgotten-export) The symbol "ExpressionTypeDefinition" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "name" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "EsRawResponse" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "EsRawResponseExpressionTypeDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type EsRawResponseExpressionTypeDefinition = ExpressionTypeDefinition;
+
// Warning: (ae-missing-release-tag) "ExistsFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -1653,7 +1672,7 @@ export const search: {
intervalOptions: ({
display: string;
val: string;
- enabled(agg: import("./search/aggs/buckets/bucket_agg_type").IBucketAggConfig): boolean | "" | undefined;
+ enabled(agg: import("../common").IBucketAggConfig): boolean | "" | undefined;
} | {
display: string;
val: string;
@@ -1662,9 +1681,9 @@ export const search: {
InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError;
Ipv4Address: typeof Ipv4Address;
isDateHistogramBucketAggConfig: typeof isDateHistogramBucketAggConfig;
- isNumberType: (agg: import("./search").AggConfig) => boolean;
- isStringType: (agg: import("./search").AggConfig) => boolean;
- isType: (...types: string[]) => (agg: import("./search").AggConfig) => boolean;
+ isNumberType: (agg: import("../common").AggConfig) => boolean;
+ isStringType: (agg: import("../common").AggConfig) => boolean;
+ isType: (...types: string[]) => (agg: import("../common").AggConfig) => boolean;
isValidEsInterval: typeof isValidEsInterval;
isValidInterval: typeof isValidInterval;
parentPipelineType: string;
@@ -1973,21 +1992,21 @@ export const UI_SETTINGS: {
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:371:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:372:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:381:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:372:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:372:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:372:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:372:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:374:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:375:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:62:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:63:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
diff --git a/src/plugins/data/public/search/aggs/aggs_service.test.ts b/src/plugins/data/public/search/aggs/aggs_service.test.ts
new file mode 100644
index 0000000000000..db25dfb300d11
--- /dev/null
+++ b/src/plugins/data/public/search/aggs/aggs_service.test.ts
@@ -0,0 +1,182 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { BehaviorSubject, Subscription } from 'rxjs';
+
+import { coreMock } from '../../../../../core/public/mocks';
+import { expressionsPluginMock } from '../../../../../plugins/expressions/public/mocks';
+import { BucketAggType, getAggTypes, MetricAggType } from '../../../common';
+import { fieldFormatsServiceMock } from '../../field_formats/mocks';
+
+import {
+ AggsService,
+ AggsSetupDependencies,
+ AggsStartDependencies,
+ createGetConfig,
+} from './aggs_service';
+
+const { uiSettings } = coreMock.createSetup();
+
+describe('AggsService - public', () => {
+ let service: AggsService;
+ let setupDeps: AggsSetupDependencies;
+ let startDeps: AggsStartDependencies;
+
+ beforeEach(() => {
+ service = new AggsService();
+ setupDeps = {
+ registerFunction: expressionsPluginMock.createSetupContract().registerFunction,
+ uiSettings,
+ };
+ startDeps = {
+ fieldFormats: fieldFormatsServiceMock.createStartContract(),
+ uiSettings,
+ };
+ });
+
+ describe('setup()', () => {
+ test('exposes proper contract', () => {
+ const setup = service.setup(setupDeps);
+ expect(Object.keys(setup).length).toBe(1);
+ expect(setup).toHaveProperty('types');
+ });
+
+ test('registers default agg types', () => {
+ service.setup(setupDeps);
+ const start = service.start(startDeps);
+ expect(start.types.getAll().buckets.length).toBe(11);
+ expect(start.types.getAll().metrics.length).toBe(21);
+ });
+
+ test('registers custom agg types', () => {
+ const setup = service.setup(setupDeps);
+ setup.types.registerBucket(
+ 'foo',
+ () => ({ name: 'foo', type: 'buckets' } as BucketAggType)
+ );
+ setup.types.registerMetric(
+ 'bar',
+ () => ({ name: 'bar', type: 'metrics' } as MetricAggType)
+ );
+
+ const start = service.start(startDeps);
+ expect(start.types.getAll().buckets.length).toBe(12);
+ expect(start.types.getAll().buckets.some(({ name }) => name === 'foo')).toBe(true);
+ expect(start.types.getAll().metrics.length).toBe(22);
+ expect(start.types.getAll().metrics.some(({ name }) => name === 'bar')).toBe(true);
+ });
+ });
+
+ describe('start()', () => {
+ test('exposes proper contract', () => {
+ const start = service.start(startDeps);
+ expect(Object.keys(start).length).toBe(3);
+ expect(start).toHaveProperty('calculateAutoTimeExpression');
+ expect(start).toHaveProperty('createAggConfigs');
+ expect(start).toHaveProperty('types');
+ });
+
+ test('types registry returns initialized agg types', () => {
+ service.setup(setupDeps);
+ const start = service.start(startDeps);
+
+ expect(start.types.get('terms').name).toBe('terms');
+ });
+
+ test('registers default agg types', () => {
+ service.setup(setupDeps);
+ const start = service.start(startDeps);
+
+ const aggTypes = getAggTypes();
+ expect(start.types.getAll().buckets.length).toBe(aggTypes.buckets.length);
+ expect(start.types.getAll().metrics.length).toBe(aggTypes.metrics.length);
+ });
+
+ test('merges default agg types with types registered during setup', () => {
+ const setup = service.setup(setupDeps);
+ setup.types.registerBucket(
+ 'foo',
+ () => ({ name: 'foo', type: 'buckets' } as BucketAggType)
+ );
+ setup.types.registerMetric(
+ 'bar',
+ () => ({ name: 'bar', type: 'metrics' } as MetricAggType)
+ );
+
+ const start = service.start(startDeps);
+
+ const aggTypes = getAggTypes();
+ expect(start.types.getAll().buckets.length).toBe(aggTypes.buckets.length + 1);
+ expect(start.types.getAll().buckets.some(({ name }) => name === 'foo')).toBe(true);
+ expect(start.types.getAll().metrics.length).toBe(aggTypes.metrics.length + 1);
+ expect(start.types.getAll().metrics.some(({ name }) => name === 'bar')).toBe(true);
+ });
+ });
+
+ describe('createGetConfig()', () => {
+ let fooSubject$: BehaviorSubject;
+ let barSubject$: BehaviorSubject;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ fooSubject$ = new BehaviorSubject('fooVal');
+ barSubject$ = new BehaviorSubject('barVal');
+
+ uiSettings.get$.mockImplementation((key: string) => {
+ const mockSettings: Record = {
+ foo: () => fooSubject$,
+ bar: () => barSubject$,
+ };
+ return mockSettings[key] ? mockSettings[key]() : undefined;
+ });
+ });
+
+ test('returns a function to get the value of the provided setting', () => {
+ const requiredSettings = ['foo', 'bar'];
+ const subscriptions: Subscription[] = [];
+ const getConfig = createGetConfig(uiSettings, requiredSettings, subscriptions);
+
+ expect(getConfig('foo')).toBe('fooVal');
+ expect(getConfig('bar')).toBe('barVal');
+ });
+
+ test('does not return values for settings that are not explicitly declared', () => {
+ const requiredSettings = ['foo', 'bar'];
+ const subscriptions: Subscription[] = [];
+ const getConfig = createGetConfig(uiSettings, requiredSettings, subscriptions);
+
+ expect(subscriptions.length).toBe(2);
+ expect(getConfig('baz')).toBe(undefined);
+ });
+
+ test('provides latest value for each setting', () => {
+ const requiredSettings = ['foo', 'bar'];
+ const subscriptions: Subscription[] = [];
+ const getConfig = createGetConfig(uiSettings, requiredSettings, subscriptions);
+
+ expect(getConfig('foo')).toBe('fooVal');
+ fooSubject$.next('fooVal2');
+ expect(getConfig('foo')).toBe('fooVal2');
+ expect(getConfig('foo')).toBe('fooVal2');
+ fooSubject$.next('fooVal3');
+ expect(getConfig('foo')).toBe('fooVal3');
+ });
+ });
+});
diff --git a/src/plugins/data/public/search/aggs/aggs_service.ts b/src/plugins/data/public/search/aggs/aggs_service.ts
new file mode 100644
index 0000000000000..d535f97fefdf8
--- /dev/null
+++ b/src/plugins/data/public/search/aggs/aggs_service.ts
@@ -0,0 +1,152 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Subscription } from 'rxjs';
+
+import { IUiSettingsClient } from 'src/core/public';
+import { ExpressionsServiceSetup } from 'src/plugins/expressions/common';
+import {
+ aggsRequiredUiSettings,
+ AggsCommonStartDependencies,
+ AggsCommonService,
+ AggConfigs,
+ AggTypesDependencies,
+ calculateBounds,
+ TimeRange,
+} from '../../../common';
+import { FieldFormatsStart } from '../../field_formats';
+import { getForceNow } from '../../query/timefilter/lib/get_force_now';
+import { AggsSetup, AggsStart } from './types';
+
+/**
+ * Aggs needs synchronous access to specific uiSettings. Since settings can change
+ * without a page refresh, we create a cache that subscribes to changes from
+ * uiSettings.get$ and keeps everything up-to-date.
+ *
+ * @internal
+ */
+export function createGetConfig(
+ uiSettings: IUiSettingsClient,
+ requiredSettings: string[],
+ subscriptions: Subscription[]
+): AggsCommonStartDependencies['getConfig'] {
+ const settingsCache: Record = {};
+
+ requiredSettings.forEach((setting) => {
+ subscriptions.push(
+ uiSettings.get$(setting).subscribe((value) => {
+ settingsCache[setting] = value;
+ })
+ );
+ });
+
+ return (key) => settingsCache[key];
+}
+
+/** @internal */
+export interface AggsSetupDependencies {
+ registerFunction: ExpressionsServiceSetup['registerFunction'];
+ uiSettings: IUiSettingsClient;
+}
+
+/** @internal */
+export interface AggsStartDependencies {
+ fieldFormats: FieldFormatsStart;
+ uiSettings: IUiSettingsClient;
+}
+
+/**
+ * The aggs service provides a means of modeling and manipulating the various
+ * Elasticsearch aggregations supported by Kibana, providing the ability to
+ * output the correct DSL when you are ready to send your request to ES.
+ */
+export class AggsService {
+ private readonly aggsCommonService = new AggsCommonService();
+ private readonly initializedAggTypes = new Map();
+ private getConfig?: AggsCommonStartDependencies['getConfig'];
+ private subscriptions: Subscription[] = [];
+
+ /**
+ * getForceNow uses window.location, so we must have a separate implementation
+ * of calculateBounds on the client and the server.
+ */
+ private calculateBounds = (timeRange: TimeRange) =>
+ calculateBounds(timeRange, { forceNow: getForceNow() });
+
+ public setup({ registerFunction, uiSettings }: AggsSetupDependencies): AggsSetup {
+ this.getConfig = createGetConfig(uiSettings, aggsRequiredUiSettings, this.subscriptions);
+
+ return this.aggsCommonService.setup({ registerFunction });
+ }
+
+ public start({ fieldFormats, uiSettings }: AggsStartDependencies): AggsStart {
+ const { calculateAutoTimeExpression, types } = this.aggsCommonService.start({
+ getConfig: this.getConfig!,
+ });
+
+ const aggTypesDependencies: AggTypesDependencies = {
+ calculateBounds: this.calculateBounds,
+ getConfig: this.getConfig!,
+ getFieldFormatsStart: () => ({
+ deserialize: fieldFormats.deserialize,
+ getDefaultInstance: fieldFormats.getDefaultInstance,
+ }),
+ isDefaultTimezone: () => uiSettings.isDefault('dateFormat:tz'),
+ };
+
+ // initialize each agg type and store in memory
+ types.getAll().buckets.forEach((type) => {
+ const agg = type(aggTypesDependencies);
+ this.initializedAggTypes.set(agg.name, agg);
+ });
+ types.getAll().metrics.forEach((type) => {
+ const agg = type(aggTypesDependencies);
+ this.initializedAggTypes.set(agg.name, agg);
+ });
+
+ const typesRegistry = {
+ get: (name: string) => {
+ return this.initializedAggTypes.get(name);
+ },
+ getAll: () => {
+ return {
+ buckets: Array.from(this.initializedAggTypes.values()).filter(
+ (agg) => agg.type === 'buckets'
+ ),
+ metrics: Array.from(this.initializedAggTypes.values()).filter(
+ (agg) => agg.type === 'metrics'
+ ),
+ };
+ },
+ };
+
+ return {
+ calculateAutoTimeExpression,
+ createAggConfigs: (indexPattern, configStates = [], schemas) => {
+ return new AggConfigs(indexPattern, configStates, { typesRegistry });
+ },
+ types: typesRegistry,
+ };
+ }
+
+ public stop() {
+ this.subscriptions.forEach((s) => s.unsubscribe());
+ this.subscriptions = [];
+ }
+}
diff --git a/src/plugins/data/public/search/aggs/index.ts b/src/plugins/data/public/search/aggs/index.ts
index 1139d9c7ff722..77d97c426260c 100644
--- a/src/plugins/data/public/search/aggs/index.ts
+++ b/src/plugins/data/public/search/aggs/index.ts
@@ -17,14 +17,5 @@
* under the License.
*/
-export * from './agg_config';
-export * from './agg_configs';
-export * from './agg_groups';
-export * from './agg_type';
-export * from './agg_types';
-export * from './agg_types_registry';
-export * from './buckets';
-export * from './metrics';
-export * from './param_types';
+export * from './aggs_service';
export * from './types';
-export * from './utils';
diff --git a/src/plugins/data/public/search/aggs/mocks.ts b/src/plugins/data/public/search/aggs/mocks.ts
index 2cad646067292..ca13343777e63 100644
--- a/src/plugins/data/public/search/aggs/mocks.ts
+++ b/src/plugins/data/public/search/aggs/mocks.ts
@@ -17,15 +17,17 @@
* under the License.
*/
-import { coreMock } from '../../../../../../src/core/public/mocks';
import {
AggConfigs,
AggTypesRegistrySetup,
AggTypesRegistryStart,
getCalculateAutoTimeExpression,
-} from './';
-import { SearchAggsSetup, SearchAggsStart } from './types';
-import { mockAggTypesRegistry } from './test_helpers';
+} from '../../../common';
+import { AggsSetup, AggsStart } from './types';
+
+import { mockAggTypesRegistry } from '../../../common/search/aggs/test_helpers';
+
+const getConfig = jest.fn();
const aggTypeBaseParamMock = () => ({
name: 'some_param',
@@ -53,21 +55,18 @@ export const aggTypesRegistrySetupMock = (): AggTypesRegistrySetup => ({
export const aggTypesRegistryStartMock = (): AggTypesRegistryStart => ({
get: jest.fn().mockImplementation(aggTypeConfigMock),
- getBuckets: jest.fn().mockImplementation(() => [aggTypeConfigMock()]),
- getMetrics: jest.fn().mockImplementation(() => [aggTypeConfigMock()]),
getAll: jest.fn().mockImplementation(() => ({
buckets: [aggTypeConfigMock()],
metrics: [aggTypeConfigMock()],
})),
});
-export const searchAggsSetupMock = (): SearchAggsSetup => ({
- calculateAutoTimeExpression: getCalculateAutoTimeExpression(coreMock.createSetup().uiSettings),
+export const searchAggsSetupMock = (): AggsSetup => ({
types: aggTypesRegistrySetupMock(),
});
-export const searchAggsStartMock = (): SearchAggsStart => ({
- calculateAutoTimeExpression: getCalculateAutoTimeExpression(coreMock.createStart().uiSettings),
+export const searchAggsStartMock = (): AggsStart => ({
+ calculateAutoTimeExpression: getCalculateAutoTimeExpression(getConfig),
createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => {
return new AggConfigs(indexPattern, configStates, {
typesRegistry: mockAggTypesRegistry(),
diff --git a/src/plugins/data/public/search/aggs/types.ts b/src/plugins/data/public/search/aggs/types.ts
index a784bfaada4c7..38be541973ce9 100644
--- a/src/plugins/data/public/search/aggs/types.ts
+++ b/src/plugins/data/public/search/aggs/types.ts
@@ -17,131 +17,7 @@
* under the License.
*/
-import { IndexPattern } from '../../index_patterns';
-import {
- AggConfigSerialized,
- AggConfigs,
- AggParamsRange,
- AggParamsIpRange,
- AggParamsDateRange,
- AggParamsFilter,
- AggParamsFilters,
- AggParamsSignificantTerms,
- AggParamsGeoTile,
- AggParamsGeoHash,
- AggParamsTerms,
- AggParamsAvg,
- AggParamsCardinality,
- AggParamsGeoBounds,
- AggParamsGeoCentroid,
- AggParamsMax,
- AggParamsMedian,
- AggParamsMin,
- AggParamsStdDeviation,
- AggParamsSum,
- AggParamsBucketAvg,
- AggParamsBucketMax,
- AggParamsBucketMin,
- AggParamsBucketSum,
- AggParamsCumulativeSum,
- AggParamsDerivative,
- AggParamsMovingAvg,
- AggParamsPercentileRanks,
- AggParamsPercentiles,
- AggParamsSerialDiff,
- AggParamsTopHit,
- AggParamsHistogram,
- AggParamsDateHistogram,
- AggTypesRegistrySetup,
- AggTypesRegistryStart,
- CreateAggConfigParams,
- getCalculateAutoTimeExpression,
- METRIC_TYPES,
- BUCKET_TYPES,
-} from './';
+import { AggsCommonSetup } from '../../../common';
-export { IAggConfig, AggConfigSerialized } from './agg_config';
-export { CreateAggConfigParams, IAggConfigs } from './agg_configs';
-export { IAggType } from './agg_type';
-export { AggParam, AggParamOption } from './agg_params';
-export { IFieldParamType } from './param_types';
-export { IMetricAggType } from './metrics/metric_agg_type';
-export { DateRangeKey } from './buckets/lib/date_range';
-export { IpRangeKey } from './buckets/lib/ip_range';
-export { OptionedValueProp } from './param_types/optioned';
-
-/** @internal */
-export interface SearchAggsSetup {
- calculateAutoTimeExpression: ReturnType;
- types: AggTypesRegistrySetup;
-}
-
-/** @internal */
-export interface SearchAggsStart {
- calculateAutoTimeExpression: ReturnType;
- createAggConfigs: (
- indexPattern: IndexPattern,
- configStates?: CreateAggConfigParams[],
- schemas?: Record
- ) => InstanceType;
- types: AggTypesRegistryStart;
-}
-
-/** @internal */
-export interface BaseAggParams {
- json?: string;
- customLabel?: string;
-}
-
-/** @internal */
-export interface AggExpressionType {
- type: 'agg_type';
- value: AggConfigSerialized;
-}
-
-/** @internal */
-export type AggExpressionFunctionArgs<
- Name extends keyof AggParamsMapping
-> = AggParamsMapping[Name] & Pick;
-
-/**
- * A global list of the param interfaces for each agg type.
- * For now this is internal, but eventually we will probably
- * want to make it public.
- *
- * @internal
- */
-export interface AggParamsMapping {
- [BUCKET_TYPES.RANGE]: AggParamsRange;
- [BUCKET_TYPES.IP_RANGE]: AggParamsIpRange;
- [BUCKET_TYPES.DATE_RANGE]: AggParamsDateRange;
- [BUCKET_TYPES.FILTER]: AggParamsFilter;
- [BUCKET_TYPES.FILTERS]: AggParamsFilters;
- [BUCKET_TYPES.SIGNIFICANT_TERMS]: AggParamsSignificantTerms;
- [BUCKET_TYPES.GEOTILE_GRID]: AggParamsGeoTile;
- [BUCKET_TYPES.GEOHASH_GRID]: AggParamsGeoHash;
- [BUCKET_TYPES.HISTOGRAM]: AggParamsHistogram;
- [BUCKET_TYPES.DATE_HISTOGRAM]: AggParamsDateHistogram;
- [BUCKET_TYPES.TERMS]: AggParamsTerms;
- [METRIC_TYPES.AVG]: AggParamsAvg;
- [METRIC_TYPES.CARDINALITY]: AggParamsCardinality;
- [METRIC_TYPES.COUNT]: BaseAggParams;
- [METRIC_TYPES.GEO_BOUNDS]: AggParamsGeoBounds;
- [METRIC_TYPES.GEO_CENTROID]: AggParamsGeoCentroid;
- [METRIC_TYPES.MAX]: AggParamsMax;
- [METRIC_TYPES.MEDIAN]: AggParamsMedian;
- [METRIC_TYPES.MIN]: AggParamsMin;
- [METRIC_TYPES.STD_DEV]: AggParamsStdDeviation;
- [METRIC_TYPES.SUM]: AggParamsSum;
- [METRIC_TYPES.AVG_BUCKET]: AggParamsBucketAvg;
- [METRIC_TYPES.MAX_BUCKET]: AggParamsBucketMax;
- [METRIC_TYPES.MIN_BUCKET]: AggParamsBucketMin;
- [METRIC_TYPES.SUM_BUCKET]: AggParamsBucketSum;
- [METRIC_TYPES.CUMULATIVE_SUM]: AggParamsCumulativeSum;
- [METRIC_TYPES.DERIVATIVE]: AggParamsDerivative;
- [METRIC_TYPES.MOVING_FN]: AggParamsMovingAvg;
- [METRIC_TYPES.PERCENTILE_RANKS]: AggParamsPercentileRanks;
- [METRIC_TYPES.PERCENTILES]: AggParamsPercentiles;
- [METRIC_TYPES.SERIAL_DIFF]: AggParamsSerialDiff;
- [METRIC_TYPES.TOP_HITS]: AggParamsTopHit;
-}
+export type AggsSetup = AggsCommonSetup;
+export { AggsStart } from '../../../common';
diff --git a/src/plugins/data/public/search/expressions/__snapshots__/es_raw_response.test.ts.snap b/src/plugins/data/public/search/expressions/__snapshots__/es_raw_response.test.ts.snap
new file mode 100644
index 0000000000000..c43663a50a2ba
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/__snapshots__/es_raw_response.test.ts.snap
@@ -0,0 +1,193 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`esRawResponse converts aggregations to table simple aggregation response 1`] = `
+Object {
+ "columns": Array [
+ Object {
+ "id": "2.buckets.key",
+ "meta": Object {
+ "field": "2.buckets.key",
+ "params": Object {},
+ "type": "string",
+ },
+ "name": "2.buckets.key",
+ },
+ Object {
+ "id": "2.buckets.doc_count",
+ "meta": Object {
+ "field": "2.buckets.doc_count",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.buckets.doc_count",
+ },
+ Object {
+ "id": "2.doc_count_error_upper_bound",
+ "meta": Object {
+ "field": "2.doc_count_error_upper_bound",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.doc_count_error_upper_bound",
+ },
+ Object {
+ "id": "2.sum_other_doc_count",
+ "meta": Object {
+ "field": "2.sum_other_doc_count",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.sum_other_doc_count",
+ },
+ ],
+ "meta": Object {
+ "source": "*",
+ "type": "esdsl",
+ },
+ "rows": Array [
+ Object {
+ "2.buckets.doc_count": 1033,
+ "2.buckets.key": "FEMALE",
+ "2.doc_count_error_upper_bound": 0,
+ "2.sum_other_doc_count": 0,
+ },
+ Object {
+ "2.buckets.doc_count": 944,
+ "2.buckets.key": "MALE",
+ "2.doc_count_error_upper_bound": 0,
+ "2.sum_other_doc_count": 0,
+ },
+ ],
+ "type": "datatable",
+}
+`;
+
+exports[`esRawResponse converts raw docs to table simple docs response 1`] = `
+Object {
+ "columns": Array [
+ Object {
+ "id": "order_date",
+ "meta": Object {
+ "field": "order_date",
+ "params": Object {},
+ "type": "object",
+ },
+ "name": "order_date",
+ },
+ Object {
+ "id": "products.created_on",
+ "meta": Object {
+ "field": "products.created_on",
+ "params": Object {},
+ "type": "object",
+ },
+ "name": "products.created_on",
+ },
+ ],
+ "meta": Object {
+ "source": "*",
+ "type": "esdsl",
+ },
+ "rows": Array [
+ Object {
+ "order_date": Array [
+ "2020-07-13T09:27:22.000Z",
+ ],
+ "products.created_on": Array [
+ "2016-12-12T09:27:22.000Z",
+ "2016-12-12T09:27:22.000Z",
+ ],
+ },
+ Object {
+ "order_date": Array [
+ "2020-07-15T08:12:29.000Z",
+ ],
+ "products.created_on": Array [
+ "2016-12-14T08:12:29.000Z",
+ "2016-12-14T08:12:29.000Z",
+ ],
+ },
+ Object {
+ "order_date": Array [
+ "2020-07-15T01:26:24.000Z",
+ ],
+ "products.created_on": Array [
+ "2016-12-14T01:26:24.000Z",
+ "2016-12-14T01:26:24.000Z",
+ ],
+ },
+ Object {
+ "order_date": Array [
+ "2020-07-10T19:55:12.000Z",
+ ],
+ "products.created_on": Array [
+ "2016-12-09T19:55:12.000Z",
+ "2016-12-09T19:55:12.000Z",
+ ],
+ },
+ ],
+ "type": "datatable",
+}
+`;
+
+exports[`esRawResponse returns aggs if both docs and aggs are present on response 1`] = `
+Object {
+ "columns": Array [
+ Object {
+ "id": "2.buckets.key",
+ "meta": Object {
+ "field": "2.buckets.key",
+ "params": Object {},
+ "type": "string",
+ },
+ "name": "2.buckets.key",
+ },
+ Object {
+ "id": "2.buckets.doc_count",
+ "meta": Object {
+ "field": "2.buckets.doc_count",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.buckets.doc_count",
+ },
+ Object {
+ "id": "2.doc_count_error_upper_bound",
+ "meta": Object {
+ "field": "2.doc_count_error_upper_bound",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.doc_count_error_upper_bound",
+ },
+ Object {
+ "id": "2.sum_other_doc_count",
+ "meta": Object {
+ "field": "2.sum_other_doc_count",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.sum_other_doc_count",
+ },
+ ],
+ "meta": Object {
+ "source": "*",
+ "type": "esdsl",
+ },
+ "rows": Array [
+ Object {
+ "2.buckets.doc_count": 1033,
+ "2.buckets.key": "FEMALE",
+ "2.doc_count_error_upper_bound": 0,
+ "2.sum_other_doc_count": 0,
+ },
+ Object {
+ "2.buckets.doc_count": 944,
+ "2.buckets.key": "MALE",
+ "2.doc_count_error_upper_bound": 0,
+ "2.sum_other_doc_count": 0,
+ },
+ ],
+ "type": "datatable",
+}
+`;
diff --git a/src/plugins/data/public/search/expressions/__snapshots__/esdsl.test.ts.snap b/src/plugins/data/public/search/expressions/__snapshots__/esdsl.test.ts.snap
new file mode 100644
index 0000000000000..cd71217276373
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/__snapshots__/esdsl.test.ts.snap
@@ -0,0 +1,191 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`esdsl correctly handles filter, query and timerange on context 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "_source": false,
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "match_phrase": Object {
+ "gender": "male",
+ },
+ },
+ ],
+ "must": Array [
+ Object {
+ "query_string": Object {
+ "query": "*",
+ "time_zone": true,
+ },
+ },
+ Object {
+ "term": Object {
+ "machine.os.keyword": "osx",
+ },
+ },
+ ],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "kibana_sample_data_logs",
+ "size": 4,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
+
+exports[`esdsl correctly handles input adds filters 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "match_phrase": Object {
+ "gender": "male",
+ },
+ },
+ ],
+ "must": Array [],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "test",
+ "size": 0,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
+
+exports[`esdsl correctly handles input adds filters to query with filters 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "_source": false,
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "match_phrase": Object {
+ "gender": "male",
+ },
+ },
+ ],
+ "must": Array [
+ Object {
+ "term": Object {
+ "machine.os.keyword": "osx",
+ },
+ },
+ ],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "kibana_sample_data_logs",
+ "size": 4,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
+
+exports[`esdsl correctly handles input adds query 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "query": Object {
+ "bool": Object {
+ "filter": Array [],
+ "must": Array [
+ Object {
+ "query_string": Object {
+ "query": "*",
+ "time_zone": true,
+ },
+ },
+ ],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "test",
+ "size": 0,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
+
+exports[`esdsl correctly handles input adds query to a query with filters 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "_source": false,
+ "query": Object {
+ "bool": Object {
+ "filter": Array [],
+ "must": Array [
+ Object {
+ "query_string": Object {
+ "query": "*",
+ "time_zone": true,
+ },
+ },
+ Object {
+ "term": Object {
+ "machine.os.keyword": "osx",
+ },
+ },
+ ],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "kibana_sample_data_logs",
+ "size": 4,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
+
+exports[`esdsl correctly handles input ignores timerange 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "query": Object {
+ "bool": Object {
+ "filter": Array [],
+ "must": Array [],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "test",
+ "size": 0,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
diff --git a/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts b/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts
index 75a4464a8e61e..7eff6f25fd828 100644
--- a/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts
+++ b/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts
@@ -19,8 +19,8 @@
import { set } from '@elastic/safer-lodash-set';
import { FormattedData } from '../../../../../plugins/inspector/public';
+import { TabbedTable } from '../../../common';
import { FormatFactory } from '../../../common/field_formats/utils';
-import { TabbedTable } from '../tabify';
import { createFilter } from './create_filter';
/**
diff --git a/src/plugins/data/public/search/expressions/create_filter.test.ts b/src/plugins/data/public/search/expressions/create_filter.test.ts
index a7fd67983cb92..7968c80628531 100644
--- a/src/plugins/data/public/search/expressions/create_filter.test.ts
+++ b/src/plugins/data/public/search/expressions/create_filter.test.ts
@@ -17,11 +17,17 @@
* under the License.
*/
+import {
+ AggConfigs,
+ IAggConfig,
+ TabbedTable,
+ isRangeFilter,
+ BytesFormat,
+ FieldFormatsGetConfigFn,
+} from '../../../common';
+import { mockAggTypesRegistry } from '../../../common/search/aggs/test_helpers';
+
import { createFilter } from './create_filter';
-import { AggConfigs, IAggConfig } from '../aggs';
-import { TabbedTable } from '../tabify';
-import { isRangeFilter, BytesFormat, FieldFormatsGetConfigFn } from '../../../common';
-import { mockAggTypesRegistry } from '../aggs/test_helpers';
describe('createFilter', () => {
let table: TabbedTable;
diff --git a/src/plugins/data/public/search/expressions/create_filter.ts b/src/plugins/data/public/search/expressions/create_filter.ts
index 94d84380e03df..09200c2e17b31 100644
--- a/src/plugins/data/public/search/expressions/create_filter.ts
+++ b/src/plugins/data/public/search/expressions/create_filter.ts
@@ -17,9 +17,7 @@
* under the License.
*/
-import { IAggConfig } from '../aggs';
-import { TabbedTable } from '../tabify';
-import { Filter } from '../../../common';
+import { Filter, IAggConfig, TabbedTable } from '../../../common';
const getOtherBucketFilterTerms = (table: TabbedTable, columnIndex: number, rowIndex: number) => {
if (rowIndex === -1) {
diff --git a/src/plugins/data/public/search/expressions/es_raw_response.test.ts b/src/plugins/data/public/search/expressions/es_raw_response.test.ts
new file mode 100644
index 0000000000000..4acb75fa4a255
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/es_raw_response.test.ts
@@ -0,0 +1,827 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EsRawResponse, esRawResponse } from './es_raw_response';
+
+jest.mock('@kbn/i18n', () => {
+ return {
+ i18n: {
+ translate: (id: string, { defaultMessage }: { defaultMessage: string }) => defaultMessage,
+ },
+ };
+});
+
+describe('esRawResponse', () => {
+ describe('converts aggregations to table', () => {
+ test('simple aggregation response', () => {
+ const response: EsRawResponse = {
+ type: 'es_raw_response',
+ body: {
+ took: 7,
+ timed_out: false,
+ _shards: {
+ total: 7,
+ successful: 7,
+ skipped: 0,
+ failed: 0,
+ },
+ hits: {
+ total: 1977,
+ max_score: 0,
+ hits: [],
+ },
+ aggregations: {
+ '2': {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'FEMALE',
+ doc_count: 1033,
+ },
+ {
+ key: 'MALE',
+ doc_count: 944,
+ },
+ ],
+ },
+ },
+ },
+ };
+ const result = esRawResponse.to!.datatable(response, {});
+ expect(result).toMatchSnapshot();
+ });
+ });
+
+ describe('converts raw docs to table', () => {
+ test('simple docs response', () => {
+ const response: EsRawResponse = {
+ type: 'es_raw_response',
+ body: {
+ took: 5,
+ timed_out: false,
+ _shards: {
+ total: 7,
+ successful: 7,
+ skipped: 0,
+ failed: 0,
+ },
+ hits: {
+ total: 1977,
+ max_score: 0,
+ hits: [
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'AncqUnMBMY_orZma2mZy',
+ _type: 'document',
+ _score: 0,
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Oliver',
+ customer_full_name: 'Oliver Rios',
+ customer_gender: 'MALE',
+ customer_id: 7,
+ customer_last_name: 'Rios',
+ customer_phone: '',
+ day_of_week: 'Monday',
+ day_of_week_i: 0,
+ email: 'oliver@rios-family.zzz',
+ manufacturer: ['Low Tide Media', 'Elitelligence'],
+ order_date: '2020-07-13T09:27:22+00:00',
+ order_id: 565855,
+ products: [
+ {
+ base_price: 20.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Low Tide Media',
+ tax_amount: 0,
+ product_id: 19919,
+ category: "Men's Clothing",
+ sku: 'ZO0417504175',
+ taxless_price: 20.99,
+ unit_discount_amount: 0,
+ min_price: 9.87,
+ _id: 'sold_product_565855_19919',
+ discount_amount: 0,
+ created_on: '2016-12-12T09:27:22+00:00',
+ product_name: 'Shirt - dark blue white',
+ price: 20.99,
+ taxful_price: 20.99,
+ base_unit_price: 20.99,
+ },
+ {
+ base_price: 24.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Elitelligence',
+ tax_amount: 0,
+ product_id: 24502,
+ category: "Men's Clothing",
+ sku: 'ZO0535205352',
+ taxless_price: 24.99,
+ unit_discount_amount: 0,
+ min_price: 12.49,
+ _id: 'sold_product_565855_24502',
+ discount_amount: 0,
+ created_on: '2016-12-12T09:27:22+00:00',
+ product_name: 'Slim fit jeans - raw blue',
+ price: 24.99,
+ taxful_price: 24.99,
+ base_unit_price: 24.99,
+ },
+ ],
+ sku: ['ZO0417504175', 'ZO0535205352'],
+ taxful_total_price: 45.98,
+ taxless_total_price: 45.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'oliver',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-13T09:27:22.000Z'],
+ 'products.created_on': ['2016-12-12T09:27:22.000Z', '2016-12-12T09:27:22.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'I3cqUnMBMY_orZma2mZy',
+ _type: 'document',
+ _score: 0,
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Boris',
+ customer_full_name: 'Boris Bradley',
+ customer_gender: 'MALE',
+ customer_id: 36,
+ customer_last_name: 'Bradley',
+ customer_phone: '',
+ day_of_week: 'Wednesday',
+ day_of_week_i: 2,
+ email: 'boris@bradley-family.zzz',
+ manufacturer: ['Microlutions', 'Elitelligence'],
+ order_date: '2020-07-15T08:12:29+00:00',
+ order_id: 568397,
+ products: [
+ {
+ base_price: 32.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Microlutions',
+ tax_amount: 0,
+ product_id: 24419,
+ category: "Men's Clothing",
+ sku: 'ZO0112101121',
+ taxless_price: 32.99,
+ unit_discount_amount: 0,
+ min_price: 17.48,
+ _id: 'sold_product_568397_24419',
+ discount_amount: 0,
+ created_on: '2016-12-14T08:12:29+00:00',
+ product_name: 'Cargo trousers - oliv',
+ price: 32.99,
+ taxful_price: 32.99,
+ base_unit_price: 32.99,
+ },
+ {
+ base_price: 28.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Elitelligence',
+ tax_amount: 0,
+ product_id: 20207,
+ category: "Men's Clothing",
+ sku: 'ZO0530405304',
+ taxless_price: 28.99,
+ unit_discount_amount: 0,
+ min_price: 13.92,
+ _id: 'sold_product_568397_20207',
+ discount_amount: 0,
+ created_on: '2016-12-14T08:12:29+00:00',
+ product_name: 'Trousers - black',
+ price: 28.99,
+ taxful_price: 28.99,
+ base_unit_price: 28.99,
+ },
+ ],
+ sku: ['ZO0112101121', 'ZO0530405304'],
+ taxful_total_price: 61.98,
+ taxless_total_price: 61.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'boris',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-15T08:12:29.000Z'],
+ 'products.created_on': ['2016-12-14T08:12:29.000Z', '2016-12-14T08:12:29.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'JHcqUnMBMY_orZma2mZy',
+ _score: 0,
+ _type: 'document',
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Oliver',
+ customer_full_name: 'Oliver Hubbard',
+ customer_gender: 'MALE',
+ customer_id: 7,
+ customer_last_name: 'Hubbard',
+ customer_phone: '',
+ day_of_week: 'Wednesday',
+ day_of_week_i: 2,
+ email: 'oliver@hubbard-family.zzz',
+ manufacturer: ['Spritechnologies', 'Microlutions'],
+ order_date: '2020-07-15T01:26:24+00:00',
+ order_id: 568044,
+ products: [
+ {
+ base_price: 14.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Spritechnologies',
+ tax_amount: 0,
+ product_id: 12799,
+ category: "Men's Clothing",
+ sku: 'ZO0630406304',
+ taxless_price: 14.99,
+ unit_discount_amount: 0,
+ min_price: 6.9,
+ _id: 'sold_product_568044_12799',
+ discount_amount: 0,
+ created_on: '2016-12-14T01:26:24+00:00',
+ product_name: 'Undershirt - dark grey multicolor',
+ price: 14.99,
+ taxful_price: 14.99,
+ base_unit_price: 14.99,
+ },
+ {
+ base_price: 16.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Microlutions',
+ tax_amount: 0,
+ product_id: 18008,
+ category: "Men's Clothing",
+ sku: 'ZO0120201202',
+ taxless_price: 16.99,
+ unit_discount_amount: 0,
+ min_price: 8.83,
+ _id: 'sold_product_568044_18008',
+ discount_amount: 0,
+ created_on: '2016-12-14T01:26:24+00:00',
+ product_name: 'Long sleeved top - purple',
+ price: 16.99,
+ taxful_price: 16.99,
+ base_unit_price: 16.99,
+ },
+ ],
+ sku: ['ZO0630406304', 'ZO0120201202'],
+ taxful_total_price: 31.98,
+ taxless_total_price: 31.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'oliver',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-15T01:26:24.000Z'],
+ 'products.created_on': ['2016-12-14T01:26:24.000Z', '2016-12-14T01:26:24.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'LHcqUnMBMY_orZma2mZy',
+ _score: 0,
+ _type: 'document',
+ _source: {
+ category: ["Women's Shoes", "Women's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Wilhemina St.',
+ customer_full_name: 'Wilhemina St. Parker',
+ customer_gender: 'FEMALE',
+ customer_id: 17,
+ customer_last_name: 'Parker',
+ customer_phone: '',
+ day_of_week: 'Friday',
+ day_of_week_i: 4,
+ email: 'wilhemina st.@parker-family.zzz',
+ manufacturer: ['Low Tide Media', 'Tigress Enterprises'],
+ order_date: '2020-07-10T19:55:12+00:00',
+ order_id: 562351,
+ products: [
+ {
+ base_price: 49.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Low Tide Media',
+ tax_amount: 0,
+ product_id: 18495,
+ category: "Women's Shoes",
+ sku: 'ZO0376403764',
+ taxless_price: 49.99,
+ unit_discount_amount: 0,
+ min_price: 25,
+ _id: 'sold_product_562351_18495',
+ discount_amount: 0,
+ created_on: '2016-12-09T19:55:12+00:00',
+ product_name: 'Ankle boots - cognac',
+ price: 49.99,
+ taxful_price: 49.99,
+ base_unit_price: 49.99,
+ },
+ {
+ base_price: 28.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Tigress Enterprises',
+ tax_amount: 0,
+ product_id: 22598,
+ category: "Women's Clothing",
+ sku: 'ZO0050800508',
+ taxless_price: 28.99,
+ unit_discount_amount: 0,
+ min_price: 14.78,
+ _id: 'sold_product_562351_22598',
+ discount_amount: 0,
+ created_on: '2016-12-09T19:55:12+00:00',
+ product_name: 'Shift dress - black',
+ price: 28.99,
+ taxful_price: 28.99,
+ base_unit_price: 28.99,
+ },
+ ],
+ sku: ['ZO0376403764', 'ZO0050800508'],
+ taxful_total_price: 78.98,
+ taxless_total_price: 78.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'wilhemina',
+ geoip: {
+ country_iso_code: 'MC',
+ location: {
+ lon: 7.4,
+ lat: 43.7,
+ },
+ continent_name: 'Europe',
+ city_name: 'Monte Carlo',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-10T19:55:12.000Z'],
+ 'products.created_on': ['2016-12-09T19:55:12.000Z', '2016-12-09T19:55:12.000Z'],
+ },
+ },
+ ],
+ },
+ },
+ };
+ const result = esRawResponse.to!.datatable(response, {});
+ expect(result).toMatchSnapshot();
+ });
+ });
+
+ test('returns aggs if both docs and aggs are present on response', () => {
+ const response: EsRawResponse = {
+ type: 'es_raw_response',
+ body: {
+ took: 5,
+ timed_out: false,
+ _shards: {
+ total: 7,
+ successful: 7,
+ skipped: 0,
+ failed: 0,
+ },
+ hits: {
+ total: 1977,
+ max_score: 0,
+ hits: [
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'AncqUnMBMY_orZma2mZy',
+ _type: 'document',
+ _score: 0,
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Oliver',
+ customer_full_name: 'Oliver Rios',
+ customer_gender: 'MALE',
+ customer_id: 7,
+ customer_last_name: 'Rios',
+ customer_phone: '',
+ day_of_week: 'Monday',
+ day_of_week_i: 0,
+ email: 'oliver@rios-family.zzz',
+ manufacturer: ['Low Tide Media', 'Elitelligence'],
+ order_date: '2020-07-13T09:27:22+00:00',
+ order_id: 565855,
+ products: [
+ {
+ base_price: 20.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Low Tide Media',
+ tax_amount: 0,
+ product_id: 19919,
+ category: "Men's Clothing",
+ sku: 'ZO0417504175',
+ taxless_price: 20.99,
+ unit_discount_amount: 0,
+ min_price: 9.87,
+ _id: 'sold_product_565855_19919',
+ discount_amount: 0,
+ created_on: '2016-12-12T09:27:22+00:00',
+ product_name: 'Shirt - dark blue white',
+ price: 20.99,
+ taxful_price: 20.99,
+ base_unit_price: 20.99,
+ },
+ {
+ base_price: 24.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Elitelligence',
+ tax_amount: 0,
+ product_id: 24502,
+ category: "Men's Clothing",
+ sku: 'ZO0535205352',
+ taxless_price: 24.99,
+ unit_discount_amount: 0,
+ min_price: 12.49,
+ _id: 'sold_product_565855_24502',
+ discount_amount: 0,
+ created_on: '2016-12-12T09:27:22+00:00',
+ product_name: 'Slim fit jeans - raw blue',
+ price: 24.99,
+ taxful_price: 24.99,
+ base_unit_price: 24.99,
+ },
+ ],
+ sku: ['ZO0417504175', 'ZO0535205352'],
+ taxful_total_price: 45.98,
+ taxless_total_price: 45.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'oliver',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-13T09:27:22.000Z'],
+ 'products.created_on': ['2016-12-12T09:27:22.000Z', '2016-12-12T09:27:22.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'I3cqUnMBMY_orZma2mZy',
+ _type: 'document',
+ _score: 0,
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Boris',
+ customer_full_name: 'Boris Bradley',
+ customer_gender: 'MALE',
+ customer_id: 36,
+ customer_last_name: 'Bradley',
+ customer_phone: '',
+ day_of_week: 'Wednesday',
+ day_of_week_i: 2,
+ email: 'boris@bradley-family.zzz',
+ manufacturer: ['Microlutions', 'Elitelligence'],
+ order_date: '2020-07-15T08:12:29+00:00',
+ order_id: 568397,
+ products: [
+ {
+ base_price: 32.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Microlutions',
+ tax_amount: 0,
+ product_id: 24419,
+ category: "Men's Clothing",
+ sku: 'ZO0112101121',
+ taxless_price: 32.99,
+ unit_discount_amount: 0,
+ min_price: 17.48,
+ _id: 'sold_product_568397_24419',
+ discount_amount: 0,
+ created_on: '2016-12-14T08:12:29+00:00',
+ product_name: 'Cargo trousers - oliv',
+ price: 32.99,
+ taxful_price: 32.99,
+ base_unit_price: 32.99,
+ },
+ {
+ base_price: 28.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Elitelligence',
+ tax_amount: 0,
+ product_id: 20207,
+ category: "Men's Clothing",
+ sku: 'ZO0530405304',
+ taxless_price: 28.99,
+ unit_discount_amount: 0,
+ min_price: 13.92,
+ _id: 'sold_product_568397_20207',
+ discount_amount: 0,
+ created_on: '2016-12-14T08:12:29+00:00',
+ product_name: 'Trousers - black',
+ price: 28.99,
+ taxful_price: 28.99,
+ base_unit_price: 28.99,
+ },
+ ],
+ sku: ['ZO0112101121', 'ZO0530405304'],
+ taxful_total_price: 61.98,
+ taxless_total_price: 61.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'boris',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-15T08:12:29.000Z'],
+ 'products.created_on': ['2016-12-14T08:12:29.000Z', '2016-12-14T08:12:29.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'JHcqUnMBMY_orZma2mZy',
+ _score: 0,
+ _type: 'document',
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Oliver',
+ customer_full_name: 'Oliver Hubbard',
+ customer_gender: 'MALE',
+ customer_id: 7,
+ customer_last_name: 'Hubbard',
+ customer_phone: '',
+ day_of_week: 'Wednesday',
+ day_of_week_i: 2,
+ email: 'oliver@hubbard-family.zzz',
+ manufacturer: ['Spritechnologies', 'Microlutions'],
+ order_date: '2020-07-15T01:26:24+00:00',
+ order_id: 568044,
+ products: [
+ {
+ base_price: 14.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Spritechnologies',
+ tax_amount: 0,
+ product_id: 12799,
+ category: "Men's Clothing",
+ sku: 'ZO0630406304',
+ taxless_price: 14.99,
+ unit_discount_amount: 0,
+ min_price: 6.9,
+ _id: 'sold_product_568044_12799',
+ discount_amount: 0,
+ created_on: '2016-12-14T01:26:24+00:00',
+ product_name: 'Undershirt - dark grey multicolor',
+ price: 14.99,
+ taxful_price: 14.99,
+ base_unit_price: 14.99,
+ },
+ {
+ base_price: 16.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Microlutions',
+ tax_amount: 0,
+ product_id: 18008,
+ category: "Men's Clothing",
+ sku: 'ZO0120201202',
+ taxless_price: 16.99,
+ unit_discount_amount: 0,
+ min_price: 8.83,
+ _id: 'sold_product_568044_18008',
+ discount_amount: 0,
+ created_on: '2016-12-14T01:26:24+00:00',
+ product_name: 'Long sleeved top - purple',
+ price: 16.99,
+ taxful_price: 16.99,
+ base_unit_price: 16.99,
+ },
+ ],
+ sku: ['ZO0630406304', 'ZO0120201202'],
+ taxful_total_price: 31.98,
+ taxless_total_price: 31.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'oliver',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-15T01:26:24.000Z'],
+ 'products.created_on': ['2016-12-14T01:26:24.000Z', '2016-12-14T01:26:24.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'LHcqUnMBMY_orZma2mZy',
+ _score: 0,
+ _type: 'document',
+ _source: {
+ category: ["Women's Shoes", "Women's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Wilhemina St.',
+ customer_full_name: 'Wilhemina St. Parker',
+ customer_gender: 'FEMALE',
+ customer_id: 17,
+ customer_last_name: 'Parker',
+ customer_phone: '',
+ day_of_week: 'Friday',
+ day_of_week_i: 4,
+ email: 'wilhemina st.@parker-family.zzz',
+ manufacturer: ['Low Tide Media', 'Tigress Enterprises'],
+ order_date: '2020-07-10T19:55:12+00:00',
+ order_id: 562351,
+ products: [
+ {
+ base_price: 49.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Low Tide Media',
+ tax_amount: 0,
+ product_id: 18495,
+ category: "Women's Shoes",
+ sku: 'ZO0376403764',
+ taxless_price: 49.99,
+ unit_discount_amount: 0,
+ min_price: 25,
+ _id: 'sold_product_562351_18495',
+ discount_amount: 0,
+ created_on: '2016-12-09T19:55:12+00:00',
+ product_name: 'Ankle boots - cognac',
+ price: 49.99,
+ taxful_price: 49.99,
+ base_unit_price: 49.99,
+ },
+ {
+ base_price: 28.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Tigress Enterprises',
+ tax_amount: 0,
+ product_id: 22598,
+ category: "Women's Clothing",
+ sku: 'ZO0050800508',
+ taxless_price: 28.99,
+ unit_discount_amount: 0,
+ min_price: 14.78,
+ _id: 'sold_product_562351_22598',
+ discount_amount: 0,
+ created_on: '2016-12-09T19:55:12+00:00',
+ product_name: 'Shift dress - black',
+ price: 28.99,
+ taxful_price: 28.99,
+ base_unit_price: 28.99,
+ },
+ ],
+ sku: ['ZO0376403764', 'ZO0050800508'],
+ taxful_total_price: 78.98,
+ taxless_total_price: 78.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'wilhemina',
+ geoip: {
+ country_iso_code: 'MC',
+ location: {
+ lon: 7.4,
+ lat: 43.7,
+ },
+ continent_name: 'Europe',
+ city_name: 'Monte Carlo',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-10T19:55:12.000Z'],
+ 'products.created_on': ['2016-12-09T19:55:12.000Z', '2016-12-09T19:55:12.000Z'],
+ },
+ },
+ ],
+ },
+ aggregations: {
+ '2': {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'FEMALE',
+ doc_count: 1033,
+ },
+ {
+ key: 'MALE',
+ doc_count: 944,
+ },
+ ],
+ },
+ },
+ },
+ };
+ const result = esRawResponse.to!.datatable(response, {});
+ expect(result).toMatchSnapshot();
+ });
+});
diff --git a/src/plugins/data/public/search/expressions/es_raw_response.ts b/src/plugins/data/public/search/expressions/es_raw_response.ts
new file mode 100644
index 0000000000000..bd0fcb3d49c54
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/es_raw_response.ts
@@ -0,0 +1,102 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { SearchResponse } from 'elasticsearch';
+import { ExpressionTypeDefinition } from '../../../../expressions/common';
+
+const name = 'es_raw_response';
+
+export interface EsRawResponse {
+ type: typeof name;
+ body: SearchResponse;
+}
+
+// flattens elasticsearch object into table rows
+function flatten(obj: any, keyPrefix = '') {
+ let topLevelKeys: Record = {};
+ const nestedRows: any[] = [];
+ const prefix = keyPrefix ? keyPrefix + '.' : '';
+ Object.keys(obj).forEach((key) => {
+ if (Array.isArray(obj[key])) {
+ nestedRows.push(
+ ...obj[key]
+ .map((nestedRow: any) => flatten(nestedRow, prefix + key))
+ .reduce((acc: any, object: any) => [...acc, ...object], [])
+ );
+ } else if (typeof obj[key] === 'object' && obj[key] !== null) {
+ const subRows = flatten(obj[key], prefix + key);
+ if (subRows.length === 1) {
+ topLevelKeys = { ...topLevelKeys, ...subRows[0] };
+ } else {
+ nestedRows.push(...subRows);
+ }
+ } else {
+ topLevelKeys[prefix + key] = obj[key];
+ }
+ });
+ if (nestedRows.length === 0) {
+ return [topLevelKeys];
+ } else {
+ return nestedRows.map((nestedRow) => ({ ...nestedRow, ...topLevelKeys }));
+ }
+}
+
+const parseRawDocs = (hits: SearchResponse['hits']) => {
+ return hits.hits.map((hit) => hit.fields || hit._source).filter((hit) => hit);
+};
+
+const convertResult = (body: SearchResponse) => {
+ return !body.aggregations ? parseRawDocs(body.hits) : flatten(body.aggregations);
+};
+
+export type EsRawResponseExpressionTypeDefinition = ExpressionTypeDefinition<
+ typeof name,
+ EsRawResponse,
+ EsRawResponse
+>;
+
+export const esRawResponse: EsRawResponseExpressionTypeDefinition = {
+ name,
+ to: {
+ datatable: (context: EsRawResponse) => {
+ const rows = convertResult(context.body);
+ const columns = rows.length
+ ? Object.keys(rows[0]).map((key) => ({
+ id: key,
+ name: key,
+ meta: {
+ type: typeof rows[0][key],
+ field: key,
+ params: {},
+ },
+ }))
+ : [];
+
+ return {
+ type: 'datatable',
+ meta: {
+ type: 'esdsl',
+ source: '*',
+ },
+ columns,
+ rows,
+ };
+ },
+ },
+};
diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts
index 690f6b1df11c3..50fbb114b39fd 100644
--- a/src/plugins/data/public/search/expressions/esaggs.ts
+++ b/src/plugins/data/public/search/expressions/esaggs.ts
@@ -19,15 +19,11 @@
import { get, hasIn } from 'lodash';
import { i18n } from '@kbn/i18n';
-
import { KibanaDatatable, KibanaDatatableColumn } from 'src/plugins/expressions/public';
import { calculateObjectHash } from '../../../../../plugins/kibana_utils/public';
import { PersistedState } from '../../../../../plugins/visualizations/public';
import { Adapters } from '../../../../../plugins/inspector/public';
-import { IAggConfigs } from '../aggs';
-import { ISearchSource } from '../search_source';
-import { tabifyAggResponse } from '../tabify';
import {
calculateBounds,
EsaggsExpressionFunctionDefinition,
@@ -38,6 +34,13 @@ import {
Query,
TimeRange,
} from '../../../common';
+import {
+ getRequestInspectorStats,
+ getResponseInspectorStats,
+ IAggConfigs,
+ tabifyAggResponse,
+} from '../../../common/search';
+
import { FilterManager } from '../../query';
import {
getFieldFormats,
@@ -45,8 +48,9 @@ import {
getQueryService,
getSearchService,
} from '../../services';
+import { ISearchSource } from '../search_source';
import { buildTabularInspectorData } from './build_tabular_inspector_data';
-import { getRequestInspectorStats, getResponseInspectorStats, serializeAggConfig } from './utils';
+import { serializeAggConfig } from './utils';
export interface RequestHandlerParams {
searchSource: ISearchSource;
diff --git a/src/plugins/data/public/search/expressions/esdsl.test.ts b/src/plugins/data/public/search/expressions/esdsl.test.ts
new file mode 100644
index 0000000000000..9458962464f65
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/esdsl.test.ts
@@ -0,0 +1,170 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { esdsl } from './esdsl';
+
+jest.mock('@kbn/i18n', () => {
+ return {
+ i18n: {
+ translate: (id: string, { defaultMessage }: { defaultMessage: string }) => defaultMessage,
+ },
+ };
+});
+
+jest.mock('../../services', () => ({
+ getUiSettings: () => ({
+ get: () => true,
+ }),
+ getSearchService: () => ({
+ search: jest.fn((params: any) => {
+ return {
+ toPromise: async () => {
+ return { rawResponse: params };
+ },
+ };
+ }),
+ }),
+}));
+
+describe('esdsl', () => {
+ describe('correctly handles input', () => {
+ test('throws on invalid json input', async () => {
+ const fn = async function () {
+ await esdsl().fn(null, { dsl: 'invalid json', index: 'test', size: 0 }, {
+ inspectorAdapters: {},
+ } as any);
+ };
+
+ let errorMessage;
+ try {
+ await fn();
+ } catch (error) {
+ errorMessage = error.message;
+ }
+ expect(errorMessage).toEqual('Unexpected token i in JSON at position 0');
+ });
+
+ test('adds filters', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ filters: [
+ {
+ meta: { index: '1', alias: 'test', negate: false, disabled: false },
+ query: { match_phrase: { gender: 'male' } },
+ },
+ ],
+ },
+ { dsl: '{}', index: 'test', size: 0 },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+
+ test('adds filters to query with filters', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ filters: [
+ {
+ meta: { index: '1', alias: 'test', negate: false, disabled: false },
+ query: { match_phrase: { gender: 'male' } },
+ },
+ ],
+ },
+ {
+ index: 'kibana_sample_data_logs',
+ size: 4,
+ dsl: '{"_source": false, "query": { "term": { "machine.os.keyword": "osx"}}}',
+ },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+
+ test('adds query', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ query: { language: 'lucene', query: '*' },
+ },
+ { dsl: '{}', index: 'test', size: 0 },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+
+ test('adds query to a query with filters', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ query: { language: 'lucene', query: '*' },
+ },
+ {
+ index: 'kibana_sample_data_logs',
+ size: 4,
+ dsl: '{ "_source": false, "query": { "term": { "machine.os.keyword": "osx"}}}',
+ },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+
+ test('ignores timerange', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ timeRange: { from: 'now-15m', to: 'now' },
+ },
+ { dsl: '{}', index: 'test', size: 0 },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+ });
+
+ test('correctly handles filter, query and timerange on context', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ query: { language: 'lucene', query: '*' },
+ timeRange: { from: 'now-15m', to: 'now' },
+ filters: [
+ {
+ meta: { index: '1', alias: 'test', negate: false, disabled: false },
+ query: { match_phrase: { gender: 'male' } },
+ },
+ ],
+ },
+ {
+ index: 'kibana_sample_data_logs',
+ size: 4,
+ dsl: '{ "_source": false, "query": { "term": { "machine.os.keyword": "osx"}}}',
+ },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+});
diff --git a/src/plugins/data/public/search/expressions/esdsl.ts b/src/plugins/data/public/search/expressions/esdsl.ts
new file mode 100644
index 0000000000000..d7b897ace29b4
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/esdsl.ts
@@ -0,0 +1,201 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import {
+ KibanaContext,
+ ExpressionFunctionDefinition,
+} from '../../../../../plugins/expressions/public';
+
+import { getSearchService, getUiSettings } from '../../services';
+import { EsRawResponse } from './es_raw_response';
+import { RequestStatistics, RequestAdapter } from '../../../../inspector/common';
+import { IEsSearchResponse } from '../../../common/search/es_search';
+import { buildEsQuery, getEsQueryConfig } from '../../../common/es_query/es_query';
+import { DataPublicPluginStart } from '../../types';
+
+const name = 'esdsl';
+
+type Input = KibanaContext | null;
+type Output = Promise;
+
+interface Arguments {
+ dsl: string;
+ index: string;
+ size: number;
+}
+
+export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition<
+ typeof name,
+ Input,
+ Arguments,
+ Output
+>;
+
+export const esdsl = (): EsdslExpressionFunctionDefinition => ({
+ name,
+ type: 'es_raw_response',
+ inputTypes: ['kibana_context', 'null'],
+ help: i18n.translate('data.search.esdsl.help', {
+ defaultMessage: 'Run Elasticsearch request',
+ }),
+ args: {
+ dsl: {
+ types: ['string'],
+ aliases: ['_', 'q', 'query'],
+ help: i18n.translate('data.search.esdsl.q.help', {
+ defaultMessage: 'Query DSL',
+ }),
+ required: true,
+ },
+ index: {
+ types: ['string'],
+ help: i18n.translate('data.search.esdsl.index.help', {
+ defaultMessage: 'ElasticSearch index to query',
+ }),
+ required: true,
+ },
+ size: {
+ types: ['number'],
+ help: i18n.translate('data.search.esdsl.size.help', {
+ defaultMessage: 'ElasticSearch searchAPI size parameter',
+ }),
+ default: 10,
+ },
+ },
+ async fn(input, args, { inspectorAdapters, abortSignal }) {
+ const searchService: DataPublicPluginStart['search'] = getSearchService();
+
+ const dsl = JSON.parse(args.dsl);
+
+ if (input) {
+ const esQueryConfigs = getEsQueryConfig(getUiSettings());
+ const query = buildEsQuery(
+ undefined, // args.index,
+ input.query || [],
+ input.filters || [],
+ esQueryConfigs
+ );
+
+ if (!dsl.query) {
+ dsl.query = query;
+ } else {
+ query.bool.must.push(dsl.query);
+ dsl.query = query;
+ }
+ }
+
+ if (!inspectorAdapters.requests) {
+ inspectorAdapters.requests = new RequestAdapter();
+ }
+
+ const request = inspectorAdapters.requests.start(
+ i18n.translate('data.search.dataRequest.title', {
+ defaultMessage: 'Data',
+ }),
+ {
+ description: i18n.translate('data.search.es_search.dataRequest.description', {
+ defaultMessage:
+ 'This request queries Elasticsearch to fetch the data for the visualization.',
+ }),
+ }
+ );
+
+ request.stats({
+ indexPattern: {
+ label: i18n.translate('data.search.es_search.indexPatternLabel', {
+ defaultMessage: 'Index pattern',
+ }),
+ value: args.index,
+ description: i18n.translate('data.search.es_search.indexPatternDescription', {
+ defaultMessage: 'The index pattern that connected to the Elasticsearch indices.',
+ }),
+ },
+ });
+
+ let res: IEsSearchResponse;
+ try {
+ res = await searchService
+ .search(
+ {
+ params: {
+ index: args.index,
+ size: args.size,
+ body: dsl,
+ },
+ },
+ { signal: abortSignal }
+ )
+ .toPromise();
+
+ const stats: RequestStatistics = {};
+ const resp = res.rawResponse;
+
+ if (resp && resp.took) {
+ stats.queryTime = {
+ label: i18n.translate('data.search.es_search.queryTimeLabel', {
+ defaultMessage: 'Query time',
+ }),
+ value: i18n.translate('data.search.es_search.queryTimeValue', {
+ defaultMessage: '{queryTime}ms',
+ values: { queryTime: resp.took },
+ }),
+ description: i18n.translate('data.search.es_search.queryTimeDescription', {
+ defaultMessage:
+ 'The time it took to process the query. ' +
+ 'Does not include the time to send the request or parse it in the browser.',
+ }),
+ };
+ }
+
+ if (resp && resp.hits) {
+ stats.hitsTotal = {
+ label: i18n.translate('data.search.es_search.hitsTotalLabel', {
+ defaultMessage: 'Hits (total)',
+ }),
+ value: `${resp.hits.total}`,
+ description: i18n.translate('data.search.es_search.hitsTotalDescription', {
+ defaultMessage: 'The number of documents that match the query.',
+ }),
+ };
+
+ stats.hits = {
+ label: i18n.translate('data.search.es_search.hitsLabel', {
+ defaultMessage: 'Hits',
+ }),
+ value: `${resp.hits.hits.length}`,
+ description: i18n.translate('data.search.es_search.hitsDescription', {
+ defaultMessage: 'The number of documents returned by the query.',
+ }),
+ };
+ }
+
+ request.stats(stats).ok({ json: resp });
+ request.json(dsl);
+
+ return {
+ type: 'es_raw_response',
+ body: resp,
+ };
+ } catch (e) {
+ request.error({ json: e });
+ throw e;
+ }
+ },
+});
diff --git a/src/plugins/data/public/search/expressions/index.ts b/src/plugins/data/public/search/expressions/index.ts
index 25839a805d8c5..02df7986479ad 100644
--- a/src/plugins/data/public/search/expressions/index.ts
+++ b/src/plugins/data/public/search/expressions/index.ts
@@ -18,4 +18,6 @@
*/
export * from './esaggs';
+export * from './es_raw_response';
+export * from './esdsl';
export * from './utils';
diff --git a/src/plugins/data/public/search/expressions/utils/index.ts b/src/plugins/data/public/search/expressions/utils/index.ts
index 0fd51f3e158a6..094536fc18437 100644
--- a/src/plugins/data/public/search/expressions/utils/index.ts
+++ b/src/plugins/data/public/search/expressions/utils/index.ts
@@ -17,5 +17,4 @@
* under the License.
*/
-export * from './courier_inspector_stats';
export * from './serialize_agg_config';
diff --git a/src/plugins/data/public/search/expressions/utils/serialize_agg_config.ts b/src/plugins/data/public/search/expressions/utils/serialize_agg_config.ts
index 78b4935077d10..6ba323b65783f 100644
--- a/src/plugins/data/public/search/expressions/utils/serialize_agg_config.ts
+++ b/src/plugins/data/public/search/expressions/utils/serialize_agg_config.ts
@@ -18,7 +18,7 @@
*/
import { KibanaDatatableColumnMeta } from '../../../../../../plugins/expressions/public';
-import { IAggConfig } from '../../aggs';
+import { IAggConfig } from '../../../../common';
import { IndexPattern } from '../../../index_patterns';
import { getSearchService } from '../../../../public/services';
diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts
index ae028df31e401..32bcd8a279036 100644
--- a/src/plugins/data/public/search/index.ts
+++ b/src/plugins/data/public/search/index.ts
@@ -17,9 +17,7 @@
* under the License.
*/
-export * from './aggs';
export * from './expressions';
-export * from './tabify';
export {
ISearch,
diff --git a/src/plugins/data/public/search/search_service.test.ts b/src/plugins/data/public/search/search_service.test.ts
index f0a017847e06a..4360a0caa7cde 100644
--- a/src/plugins/data/public/search/search_service.test.ts
+++ b/src/plugins/data/public/search/search_service.test.ts
@@ -19,9 +19,8 @@
import { coreMock } from '../../../../core/public/mocks';
import { CoreSetup, CoreStart } from '../../../../core/public';
-import { expressionsPluginMock } from '../../../../plugins/expressions/public/mocks';
-import { SearchService } from './search_service';
+import { SearchService, SearchServiceSetupDependencies } from './search_service';
describe('Search service', () => {
let searchService: SearchService;
@@ -36,11 +35,12 @@ describe('Search service', () => {
describe('setup()', () => {
it('exposes proper contract', async () => {
- const setup = searchService.setup(mockCoreSetup, {
+ const setup = searchService.setup(mockCoreSetup, ({
packageInfo: { version: '8' },
- expressions: expressionsPluginMock.createSetupContract(),
- } as any);
+ expressions: { registerFunction: jest.fn(), registerType: jest.fn() },
+ } as unknown) as SearchServiceSetupDependencies);
expect(setup).toHaveProperty('aggs');
+ expect(setup).toHaveProperty('usageCollector');
expect(setup).toHaveProperty('__enhance');
});
});
@@ -50,6 +50,7 @@ describe('Search service', () => {
const start = searchService.start(mockCoreStart, {
indexPatterns: {},
} as any);
+ expect(start).toHaveProperty('aggs');
expect(start).toHaveProperty('search');
});
});
diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts
index 4c94925b66d6e..04e1a46c84652 100644
--- a/src/plugins/data/public/search/search_service.ts
+++ b/src/plugins/data/public/search/search_service.ts
@@ -17,80 +17,45 @@
* under the License.
*/
-import { Plugin, CoreSetup, CoreStart, PackageInfo } from '../../../../core/public';
+import { Plugin, CoreSetup, CoreStart, PackageInfo } from 'src/core/public';
import { ISearchSetup, ISearchStart, SearchEnhancements } from './types';
-import { ExpressionsSetup } from '../../../../plugins/expressions/public';
import { createSearchSource, SearchSource, SearchSourceDependencies } from './search_source';
import { getEsClient, LegacyApiCaller } from './legacy';
-import { getForceNow } from '../query/timefilter/lib/get_force_now';
-import { calculateBounds, TimeRange } from '../../common/query';
-
+import { AggsService, AggsStartDependencies } from './aggs';
import { IndexPatternsContract } from '../index_patterns/index_patterns';
-import { GetInternalStartServicesFn } from '../types';
import { ISearchInterceptor, SearchInterceptor } from './search_interceptor';
-import {
- getAggTypes,
- getAggTypesFunctions,
- AggTypesRegistry,
- AggConfigs,
- getCalculateAutoTimeExpression,
-} from './aggs';
import { ISearchGeneric } from './types';
import { SearchUsageCollector, createUsageCollector } from './collectors';
import { UsageCollectionSetup } from '../../../usage_collection/public';
+import { esdsl, esRawResponse } from './expressions';
+import { ExpressionsSetup } from '../../../expressions/public';
-interface SearchServiceSetupDependencies {
- expressions: ExpressionsSetup;
- usageCollection?: UsageCollectionSetup;
- getInternalStartServices: GetInternalStartServicesFn;
+/** @internal */
+export interface SearchServiceSetupDependencies {
packageInfo: PackageInfo;
+ usageCollection?: UsageCollectionSetup;
+ expressions: ExpressionsSetup;
}
-interface SearchServiceStartDependencies {
+/** @internal */
+export interface SearchServiceStartDependencies {
+ fieldFormats: AggsStartDependencies['fieldFormats'];
indexPatterns: IndexPatternsContract;
}
export class SearchService implements Plugin {
private esClient?: LegacyApiCaller;
- private readonly aggTypesRegistry = new AggTypesRegistry();
+ private readonly aggsService = new AggsService();
private searchInterceptor!: ISearchInterceptor;
private usageCollector?: SearchUsageCollector;
- /**
- * getForceNow uses window.location, so we must have a separate implementation
- * of calculateBounds on the client and the server.
- */
- private calculateBounds = (timeRange: TimeRange) =>
- calculateBounds(timeRange, { forceNow: getForceNow() });
-
public setup(
core: CoreSetup,
- {
- expressions,
- usageCollection,
- packageInfo,
- getInternalStartServices,
- }: SearchServiceSetupDependencies
+ { packageInfo, usageCollection, expressions }: SearchServiceSetupDependencies
): ISearchSetup {
this.usageCollector = createUsageCollector(core, usageCollection);
this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo);
-
- const aggTypesSetup = this.aggTypesRegistry.setup();
-
- // register each agg type
- const aggTypes = getAggTypes({
- calculateBounds: this.calculateBounds,
- getInternalStartServices,
- uiSettings: core.uiSettings,
- });
- aggTypes.buckets.forEach((b) => aggTypesSetup.registerBucket(b));
- aggTypes.metrics.forEach((m) => aggTypesSetup.registerMetric(m));
-
- // register expression functions for each agg type
- const aggFunctions = getAggTypesFunctions();
- aggFunctions.forEach((fn) => expressions.registerFunction(fn));
-
/**
* A global object that intercepts all searches and provides convenience methods for cancelling
* all pending search requests, as well as getting the number of pending search requests.
@@ -108,21 +73,25 @@ export class SearchService implements Plugin {
core.injectedMetadata.getInjectedVar('esRequestTimeout') as number
);
+ expressions.registerFunction(esdsl);
+ expressions.registerType(esRawResponse);
+
return {
+ aggs: this.aggsService.setup({
+ registerFunction: expressions.registerFunction,
+ uiSettings: core.uiSettings,
+ }),
usageCollector: this.usageCollector!,
__enhance: (enhancements: SearchEnhancements) => {
this.searchInterceptor = enhancements.searchInterceptor;
},
- aggs: {
- calculateAutoTimeExpression: getCalculateAutoTimeExpression(core.uiSettings),
- types: aggTypesSetup,
- },
};
}
- public start(core: CoreStart, dependencies: SearchServiceStartDependencies): ISearchStart {
- const aggTypesStart = this.aggTypesRegistry.start();
-
+ public start(
+ { application, http, injectedMetadata, notifications, uiSettings }: CoreStart,
+ { fieldFormats, indexPatterns }: SearchServiceStartDependencies
+ ): ISearchStart {
const search: ISearchGeneric = (request, options) => {
return this.searchInterceptor.search(request, options);
};
@@ -132,25 +101,17 @@ export class SearchService implements Plugin {
};
const searchSourceDependencies: SearchSourceDependencies = {
- uiSettings: core.uiSettings,
- injectedMetadata: core.injectedMetadata,
+ uiSettings,
+ injectedMetadata,
search,
legacySearch,
};
return {
- aggs: {
- calculateAutoTimeExpression: getCalculateAutoTimeExpression(core.uiSettings),
- createAggConfigs: (indexPattern, configStates = [], schemas) => {
- return new AggConfigs(indexPattern, configStates, {
- typesRegistry: aggTypesStart,
- });
- },
- types: aggTypesStart,
- },
+ aggs: this.aggsService.start({ fieldFormats, uiSettings }),
search,
searchSource: {
- create: createSearchSource(dependencies.indexPatterns, searchSourceDependencies),
+ create: createSearchSource(indexPatterns, searchSourceDependencies),
createEmpty: () => {
return new SearchSource({}, searchSourceDependencies);
},
@@ -159,5 +120,7 @@ export class SearchService implements Plugin {
};
}
- public stop() {}
+ public stop() {
+ this.aggsService.stop();
+ }
}
diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts
index d85d4c4e5c935..d1a4437943402 100644
--- a/src/plugins/data/public/search/types.ts
+++ b/src/plugins/data/public/search/types.ts
@@ -19,11 +19,11 @@
import { Observable } from 'rxjs';
import { PackageInfo } from 'kibana/server';
-import { SearchAggsSetup, SearchAggsStart } from './aggs';
import { LegacyApiCaller } from './legacy/es_client';
import { ISearchInterceptor } from './search_interceptor';
import { ISearchSource, SearchSourceFields } from './search_source';
import { SearchUsageCollector } from './collectors';
+import { AggsSetup, AggsSetupDependencies, AggsStartDependencies, AggsStart } from './aggs';
import {
IKibanaSearchRequest,
IKibanaSearchResponse,
@@ -31,9 +31,7 @@ import {
IEsSearchResponse,
} from '../../common/search';
import { IndexPatternsContract } from '../../common/index_patterns/index_patterns';
-import { ExpressionsSetup } from '../../../expressions/public';
import { UsageCollectionSetup } from '../../../usage_collection/public';
-import { GetInternalStartServicesFn } from '../types';
export interface ISearchOptions {
signal?: AbortSignal;
@@ -62,7 +60,7 @@ export interface SearchEnhancements {
* point.
*/
export interface ISearchSetup {
- aggs: SearchAggsSetup;
+ aggs: AggsSetup;
usageCollector?: SearchUsageCollector;
/**
* @internal
@@ -71,7 +69,7 @@ export interface ISearchSetup {
}
export interface ISearchStart {
- aggs: SearchAggsStart;
+ aggs: AggsStart;
search: ISearchGeneric;
searchSource: {
create: (fields?: SearchSourceFields) => Promise;
@@ -86,13 +84,15 @@ export interface ISearchStart {
export { SEARCH_EVENT_TYPE } from './collectors';
+/** @internal */
export interface SearchServiceSetupDependencies {
- expressions: ExpressionsSetup;
- usageCollection?: UsageCollectionSetup;
- getInternalStartServices: GetInternalStartServicesFn;
packageInfo: PackageInfo;
+ registerFunction: AggsSetupDependencies['registerFunction'];
+ usageCollection?: UsageCollectionSetup;
}
+/** @internal */
export interface SearchServiceStartDependencies {
+ fieldFormats: AggsStartDependencies['fieldFormats'];
indexPatterns: IndexPatternsContract;
}
diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts
index c39b7d355d495..bffc10642eb47 100644
--- a/src/plugins/data/public/types.ts
+++ b/src/plugins/data/public/types.ts
@@ -82,15 +82,3 @@ export interface IDataPluginServices extends Partial {
storage: IStorageWrapper;
data: DataPublicPluginStart;
}
-
-/** @internal **/
-export interface InternalStartServices {
- readonly fieldFormats: FieldFormatsStart;
- readonly notifications: CoreStart['notifications'];
- readonly uiSettings: CoreStart['uiSettings'];
- readonly searchService: DataPublicPluginStart['search'];
- readonly injectedMetadata: CoreStart['injectedMetadata'];
-}
-
-/** @internal **/
-export type GetInternalStartServicesFn = () => InternalStartServices;
diff --git a/src/plugins/data/public/ui/query_string_input/_query_bar.scss b/src/plugins/data/public/ui/query_string_input/_query_bar.scss
index 007be9da63e49..00895ec49003b 100644
--- a/src/plugins/data/public/ui/query_string_input/_query_bar.scss
+++ b/src/plugins/data/public/ui/query_string_input/_query_bar.scss
@@ -59,6 +59,7 @@
&.kbnQueryBar__datePickerWrapper-isHidden {
width: 0;
overflow: hidden;
+ max-width: 0;
}
}
}
diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx
index 18b1237895f79..aee8d1f4eac4d 100644
--- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx
+++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx
@@ -22,7 +22,6 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { ShardFailureOpenModalButton } from './shard_failure_open_modal_button';
import { shardFailureRequest } from './__mocks__/shard_failure_request';
import { shardFailureResponse } from './__mocks__/shard_failure_response';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
describe('ShardFailureOpenModalButton', () => {
diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts
index 73ed88850d787..c3b06992dba0e 100644
--- a/src/plugins/data/server/index.ts
+++ b/src/plugins/data/server/index.ts
@@ -150,6 +150,16 @@ export {
*/
import {
+ // aggs
+ CidrMask,
+ intervalOptions,
+ isNumberType,
+ isStringType,
+ isType,
+ parentPipelineType,
+ propFilter,
+ siblingPipelineType,
+ termsAggFilter,
dateHistogramInterval,
InvalidEsCalendarIntervalError,
InvalidEsIntervalFormatError,
@@ -159,13 +169,41 @@ import {
parseEsInterval,
parseInterval,
toAbsoluteDates,
+ // expressions utils
+ getRequestInspectorStats,
+ getResponseInspectorStats,
+ // tabify
+ tabifyAggResponse,
+ tabifyGetColumns,
} from '../common';
export {
+ // aggs
+ AggGroupLabels,
+ AggGroupName,
+ AggGroupNames,
+ AggParam,
+ AggParamOption,
+ AggParamType,
+ AggConfigOptions,
+ BUCKET_TYPES,
EsaggsExpressionFunctionDefinition,
+ IAggConfig,
+ IAggConfigs,
+ IAggType,
+ IFieldParamType,
+ IMetricAggType,
+ METRIC_TYPES,
+ OptionedParamType,
+ OptionedValueProp,
ParsedInterval,
+ // search
IEsSearchRequest,
IEsSearchResponse,
+ // tabify
+ TabbedAggColumn,
+ TabbedAggRow,
+ TabbedTable,
} from '../common';
export {
@@ -182,16 +220,29 @@ export {
// Search namespace
export const search = {
aggs: {
+ CidrMask,
dateHistogramInterval,
+ intervalOptions,
InvalidEsCalendarIntervalError,
InvalidEsIntervalFormatError,
Ipv4Address,
+ isNumberType,
+ isStringType,
+ isType,
isValidEsInterval,
isValidInterval,
+ parentPipelineType,
parseEsInterval,
parseInterval,
+ propFilter,
+ siblingPipelineType,
+ termsAggFilter,
toAbsoluteDates,
},
+ getRequestInspectorStats,
+ getResponseInspectorStats,
+ tabifyAggResponse,
+ tabifyGetColumns,
};
/**
diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts
index 61d8e566d2d2b..5163bfcb17d40 100644
--- a/src/plugins/data/server/plugin.ts
+++ b/src/plugins/data/server/plugin.ts
@@ -17,13 +17,8 @@
* under the License.
*/
-import {
- PluginInitializerContext,
- CoreSetup,
- CoreStart,
- Plugin,
- Logger,
-} from '../../../core/server';
+import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from 'src/core/server';
+import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
import { ConfigSchema } from '../config';
import { IndexPatternsService, IndexPatternsServiceStart } from './index_patterns';
import { ISearchSetup, ISearchStart } from './search';
@@ -48,10 +43,21 @@ export interface DataPluginStart {
}
export interface DataPluginSetupDependencies {
+ expressions: ExpressionsServerSetup;
usageCollection?: UsageCollectionSetup;
}
-export class DataServerPlugin implements Plugin {
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface DataPluginStartDependencies {}
+
+export class DataServerPlugin
+ implements
+ Plugin<
+ DataPluginSetup,
+ DataPluginStart,
+ DataPluginSetupDependencies,
+ DataPluginStartDependencies
+ > {
private readonly searchService: SearchService;
private readonly scriptsService: ScriptsService;
private readonly kqlTelemetryService: KqlTelemetryService;
@@ -70,8 +76,8 @@ export class DataServerPlugin implements Plugin,
- { usageCollection }: DataPluginSetupDependencies
+ core: CoreSetup,
+ { expressions, usageCollection }: DataPluginSetupDependencies
) {
this.indexPatterns.setup(core);
this.scriptsService.setup(core);
@@ -82,7 +88,10 @@ export class DataServerPlugin implements Plugin {
+ let service: AggsService;
+ let setupDeps: AggsSetupDependencies;
+ let startDeps: AggsStartDependencies;
+
+ beforeEach(() => {
+ service = new AggsService();
+ setupDeps = {
+ registerFunction: expressionsPluginMock.createSetupContract().registerFunction,
+ };
+ startDeps = {
+ fieldFormats: createFieldFormatsStartMock(),
+ uiSettings,
+ };
+ });
+
+ describe('setup()', () => {
+ test('exposes proper contract', () => {
+ const setup = service.setup(setupDeps);
+ expect(Object.keys(setup).length).toBe(1);
+ expect(setup).toHaveProperty('types');
+ });
+ });
+
+ describe('start()', () => {
+ test('exposes proper contract', async () => {
+ service.setup(setupDeps);
+ const start = service.start(startDeps);
+
+ expect(Object.keys(start).length).toBe(1);
+ expect(start).toHaveProperty('asScopedToClient');
+
+ const contract = await start.asScopedToClient(
+ savedObjects.getScopedClient({} as KibanaRequest)
+ );
+ expect(contract).toHaveProperty('calculateAutoTimeExpression');
+ expect(contract).toHaveProperty('createAggConfigs');
+ expect(contract).toHaveProperty('types');
+ });
+
+ test('types registry returns initialized agg types', async () => {
+ service.setup(setupDeps);
+ const start = await service
+ .start(startDeps)
+ .asScopedToClient(savedObjects.getScopedClient({} as KibanaRequest));
+
+ expect(start.types.get('terms').name).toBe('terms');
+ });
+
+ test('registers default agg types', async () => {
+ service.setup(setupDeps);
+ const start = await service
+ .start(startDeps)
+ .asScopedToClient(savedObjects.getScopedClient({} as KibanaRequest));
+
+ const aggTypes = getAggTypes();
+ expect(start.types.getAll().buckets.length).toBe(aggTypes.buckets.length);
+ expect(start.types.getAll().metrics.length).toBe(aggTypes.metrics.length);
+ });
+
+ test('merges default agg types with types registered during setup', async () => {
+ const setup = service.setup(setupDeps);
+ setup.types.registerBucket(
+ 'foo',
+ () => ({ name: 'foo', type: 'buckets' } as BucketAggType)
+ );
+ setup.types.registerMetric(
+ 'bar',
+ () => ({ name: 'bar', type: 'metrics' } as MetricAggType)
+ );
+
+ const start = await service
+ .start(startDeps)
+ .asScopedToClient(savedObjects.getScopedClient({} as KibanaRequest));
+
+ const aggTypes = getAggTypes();
+ expect(start.types.getAll().buckets.length).toBe(aggTypes.buckets.length + 1);
+ expect(start.types.getAll().buckets.some(({ name }) => name === 'foo')).toBe(true);
+ expect(start.types.getAll().metrics.length).toBe(aggTypes.metrics.length + 1);
+ expect(start.types.getAll().metrics.some(({ name }) => name === 'bar')).toBe(true);
+ });
+ });
+});
diff --git a/src/plugins/data/server/search/aggs/aggs_service.ts b/src/plugins/data/server/search/aggs/aggs_service.ts
new file mode 100644
index 0000000000000..3e5cd8adb44a6
--- /dev/null
+++ b/src/plugins/data/server/search/aggs/aggs_service.ts
@@ -0,0 +1,122 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { pick } from 'lodash';
+
+import { UiSettingsServiceStart, SavedObjectsClientContract } from 'src/core/server';
+import { ExpressionsServiceSetup } from 'src/plugins/expressions/common';
+import {
+ AggsCommonService,
+ AggConfigs,
+ AggTypesDependencies,
+ aggsRequiredUiSettings,
+ calculateBounds,
+ TimeRange,
+} from '../../../common';
+import { FieldFormatsStart } from '../../field_formats';
+import { AggsSetup, AggsStart } from './types';
+
+/** @internal */
+export interface AggsSetupDependencies {
+ registerFunction: ExpressionsServiceSetup['registerFunction'];
+}
+
+/** @internal */
+export interface AggsStartDependencies {
+ fieldFormats: FieldFormatsStart;
+ uiSettings: UiSettingsServiceStart;
+}
+
+/**
+ * The aggs service provides a means of modeling and manipulating the various
+ * Elasticsearch aggregations supported by Kibana, providing the ability to
+ * output the correct DSL when you are ready to send your request to ES.
+ */
+export class AggsService {
+ private readonly aggsCommonService = new AggsCommonService();
+
+ /**
+ * getForceNow uses window.location on the client, so we must have a
+ * separate implementation of calculateBounds on the server.
+ */
+ private calculateBounds = (timeRange: TimeRange) => calculateBounds(timeRange, {});
+
+ public setup({ registerFunction }: AggsSetupDependencies): AggsSetup {
+ return this.aggsCommonService.setup({ registerFunction });
+ }
+
+ public start({ fieldFormats, uiSettings }: AggsStartDependencies): AggsStart {
+ return {
+ asScopedToClient: async (savedObjectsClient: SavedObjectsClientContract) => {
+ const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient);
+ const formats = await fieldFormats.fieldFormatServiceFactory(uiSettingsClient);
+
+ // cache ui settings, only including items which are explicitly needed by aggs
+ const uiSettingsCache = pick(await uiSettingsClient.getAll(), aggsRequiredUiSettings);
+ const getConfig = (key: string): T => {
+ return uiSettingsCache[key];
+ };
+
+ const { calculateAutoTimeExpression, types } = this.aggsCommonService.start({ getConfig });
+
+ const aggTypesDependencies: AggTypesDependencies = {
+ calculateBounds: this.calculateBounds,
+ getConfig,
+ getFieldFormatsStart: () => ({
+ deserialize: formats.deserialize,
+ getDefaultInstance: formats.getDefaultInstance,
+ }),
+ /**
+ * Date histogram and date range need to know whether we are using the
+ * default timezone, but `isDefault` is not currently offered on the
+ * server, so we need to manually check for the default value.
+ */
+ isDefaultTimezone: () => getConfig('dateFormat:tz') === 'Browser',
+ };
+
+ const typesRegistry = {
+ get: (name: string) => {
+ const type = types.get(name);
+ if (!type) {
+ return;
+ }
+ return type(aggTypesDependencies);
+ },
+ getAll: () => {
+ return {
+ // initialize each agg type on the fly
+ buckets: types.getAll().buckets.map((type) => type(aggTypesDependencies)),
+ metrics: types.getAll().metrics.map((type) => type(aggTypesDependencies)),
+ };
+ },
+ };
+
+ return {
+ calculateAutoTimeExpression,
+ createAggConfigs: (indexPattern, configStates = [], schemas) => {
+ return new AggConfigs(indexPattern, configStates, { typesRegistry });
+ },
+ types: typesRegistry,
+ };
+ },
+ };
+ }
+
+ public stop() {}
+}
diff --git a/src/plugins/data/server/search/aggs/index.ts b/src/plugins/data/server/search/aggs/index.ts
new file mode 100644
index 0000000000000..77d97c426260c
--- /dev/null
+++ b/src/plugins/data/server/search/aggs/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export * from './aggs_service';
+export * from './types';
diff --git a/src/plugins/data/server/search/aggs/mocks.ts b/src/plugins/data/server/search/aggs/mocks.ts
new file mode 100644
index 0000000000000..b50e22fe87b7c
--- /dev/null
+++ b/src/plugins/data/server/search/aggs/mocks.ts
@@ -0,0 +1,81 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ AggConfigs,
+ AggTypesRegistrySetup,
+ AggTypesRegistryStart,
+ AggsCommonStart,
+ getCalculateAutoTimeExpression,
+} from '../../../common';
+import { AggsSetup, AggsStart } from './types';
+
+import { mockAggTypesRegistry } from '../../../common/search/aggs/test_helpers';
+
+const getConfig = jest.fn();
+
+const aggTypeBaseParamMock = () => ({
+ name: 'some_param',
+ type: 'some_param_type',
+ displayName: 'some_agg_type_param',
+ required: false,
+ advanced: false,
+ default: {},
+ write: jest.fn(),
+ serialize: jest.fn().mockImplementation(() => {}),
+ deserialize: jest.fn().mockImplementation(() => {}),
+ options: [],
+});
+
+const aggTypeConfigMock = () => ({
+ name: 'some_name',
+ title: 'some_title',
+ params: [aggTypeBaseParamMock()],
+});
+
+export const aggTypesRegistrySetupMock = (): AggTypesRegistrySetup => ({
+ registerBucket: jest.fn(),
+ registerMetric: jest.fn(),
+});
+
+export const aggTypesRegistryStartMock = (): AggTypesRegistryStart => ({
+ get: jest.fn().mockImplementation(aggTypeConfigMock),
+ getAll: jest.fn().mockImplementation(() => ({
+ buckets: [aggTypeConfigMock()],
+ metrics: [aggTypeConfigMock()],
+ })),
+});
+
+export const searchAggsSetupMock = (): AggsSetup => ({
+ types: aggTypesRegistrySetupMock(),
+});
+
+const commonStartMock = (): AggsCommonStart => ({
+ calculateAutoTimeExpression: getCalculateAutoTimeExpression(getConfig),
+ createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => {
+ return new AggConfigs(indexPattern, configStates, {
+ typesRegistry: mockAggTypesRegistry(),
+ });
+ }),
+ types: mockAggTypesRegistry(),
+});
+
+export const searchAggsStartMock = (): AggsStart => ({
+ asScopedToClient: jest.fn().mockResolvedValue(commonStartMock()),
+});
diff --git a/src/plugins/data/server/search/aggs/types.ts b/src/plugins/data/server/search/aggs/types.ts
new file mode 100644
index 0000000000000..1b21d948b25d9
--- /dev/null
+++ b/src/plugins/data/server/search/aggs/types.ts
@@ -0,0 +1,27 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { SavedObjectsClientContract } from 'src/core/server';
+import { AggsCommonSetup, AggsStart as Start } from '../../../common';
+
+export type AggsSetup = AggsCommonSetup;
+
+export interface AggsStart {
+ asScopedToClient: (savedObjectsClient: SavedObjectsClientContract) => Promise;
+}
diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts
index cea2714671f0b..4a3990621ca39 100644
--- a/src/plugins/data/server/search/index.ts
+++ b/src/plugins/data/server/search/index.ts
@@ -22,3 +22,5 @@ export { ISearchStrategy, ISearchOptions, ISearchSetup, ISearchStart } from './t
export { getDefaultSearchParams, getTotalLoaded } from './es_search';
export { usageProvider, SearchUsage } from './collectors';
+
+export * from './aggs';
diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts
index b210df3c55db9..578a170f468bf 100644
--- a/src/plugins/data/server/search/mocks.ts
+++ b/src/plugins/data/server/search/mocks.ts
@@ -17,14 +17,19 @@
* under the License.
*/
-export function createSearchSetupMock() {
+import { ISearchSetup, ISearchStart } from './types';
+import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks';
+
+export function createSearchSetupMock(): jest.Mocked {
return {
+ aggs: searchAggsSetupMock(),
registerSearchStrategy: jest.fn(),
};
}
-export function createSearchStartMock() {
+export function createSearchStartMock(): jest.Mocked {
return {
+ aggs: searchAggsStartMock(),
getSearchStrategy: jest.fn(),
search: jest.fn(),
};
diff --git a/src/plugins/data/server/search/search_service.test.ts b/src/plugins/data/server/search/search_service.test.ts
index be00b7409fe4a..030f37d0f7c46 100644
--- a/src/plugins/data/server/search/search_service.test.ts
+++ b/src/plugins/data/server/search/search_service.test.ts
@@ -17,15 +17,18 @@
* under the License.
*/
+import { CoreSetup, CoreStart } from '../../../../core/server';
import { coreMock } from '../../../../core/server/mocks';
-import { SearchService } from './search_service';
-import { CoreSetup } from '../../../../core/server';
import { DataPluginStart } from '../plugin';
+import { createFieldFormatsStartMock } from '../field_formats/mocks';
+
+import { SearchService, SearchServiceSetupDependencies } from './search_service';
describe('Search service', () => {
let plugin: SearchService;
let mockCoreSetup: MockedKeys>;
+ let mockCoreStart: MockedKeys;
beforeEach(() => {
const mockLogger: any = {
@@ -33,19 +36,27 @@ describe('Search service', () => {
};
plugin = new SearchService(coreMock.createPluginInitializerContext({}), mockLogger);
mockCoreSetup = coreMock.createSetup();
+ mockCoreStart = coreMock.createStart();
});
describe('setup()', () => {
it('exposes proper contract', async () => {
- const setup = plugin.setup(mockCoreSetup, {});
+ const setup = plugin.setup(mockCoreSetup, ({
+ packageInfo: { version: '8' },
+ registerFunction: jest.fn(),
+ } as unknown) as SearchServiceSetupDependencies);
+ expect(setup).toHaveProperty('aggs');
expect(setup).toHaveProperty('registerSearchStrategy');
});
});
describe('start()', () => {
it('exposes proper contract', async () => {
- const setup = plugin.start();
- expect(setup).toHaveProperty('getSearchStrategy');
+ const start = plugin.start(mockCoreStart, {
+ fieldFormats: createFieldFormatsStartMock(),
+ });
+ expect(start).toHaveProperty('aggs');
+ expect(start).toHaveProperty('getSearchStrategy');
});
});
});
diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts
index 9dc47369567af..a8b1cdd608a84 100644
--- a/src/plugins/data/server/search/search_service.ts
+++ b/src/plugins/data/server/search/search_service.ts
@@ -18,13 +18,18 @@
*/
import {
+ CoreSetup,
+ CoreStart,
+ Logger,
Plugin,
PluginInitializerContext,
- CoreSetup,
RequestHandlerContext,
- Logger,
} from '../../../../core/server';
import { ISearchSetup, ISearchStart, ISearchStrategy } from './types';
+
+import { AggsService, AggsSetupDependencies } from './aggs';
+
+import { FieldFormatsStart } from '../field_formats';
import { registerSearchRoute } from './routes';
import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './es_search';
import { DataPluginStart } from '../plugin';
@@ -38,7 +43,19 @@ interface StrategyMap {
[name: string]: ISearchStrategy;
}
+/** @internal */
+export interface SearchServiceSetupDependencies {
+ registerFunction: AggsSetupDependencies['registerFunction'];
+ usageCollection?: UsageCollectionSetup;
+}
+
+/** @internal */
+export interface SearchServiceStartDependencies {
+ fieldFormats: FieldFormatsStart;
+}
+
export class SearchService implements Plugin {
+ private readonly aggsService = new AggsService();
private searchStrategies: StrategyMap = {};
constructor(
@@ -48,7 +65,7 @@ export class SearchService implements Plugin {
public setup(
core: CoreSetup,
- { usageCollection }: { usageCollection?: UsageCollectionSetup }
+ { registerFunction, usageCollection }: SearchServiceSetupDependencies
): ISearchSetup {
const usage = usageCollection ? usageProvider(core) : undefined;
@@ -68,7 +85,11 @@ export class SearchService implements Plugin {
registerSearchRoute(core);
- return { registerSearchStrategy: this.registerSearchStrategy, usage };
+ return {
+ aggs: this.aggsService.setup({ registerFunction }),
+ registerSearchStrategy: this.registerSearchStrategy,
+ usage,
+ };
}
private search(
@@ -83,8 +104,12 @@ export class SearchService implements Plugin {
);
}
- public start(): ISearchStart {
+ public start(
+ { uiSettings }: CoreStart,
+ { fieldFormats }: SearchServiceStartDependencies
+ ): ISearchStart {
return {
+ aggs: this.aggsService.start({ fieldFormats, uiSettings }),
getSearchStrategy: this.getSearchStrategy,
search: (
context: RequestHandlerContext,
@@ -96,7 +121,9 @@ export class SearchService implements Plugin {
};
}
- public stop() {}
+ public stop() {
+ this.aggsService.stop();
+ }
private registerSearchStrategy = (name: string, strategy: ISearchStrategy) => {
this.logger.info(`Register strategy ${name}`);
diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts
index 76afd7e8c951c..fe54975d76624 100644
--- a/src/plugins/data/server/search/types.ts
+++ b/src/plugins/data/server/search/types.ts
@@ -19,6 +19,7 @@
import { RequestHandlerContext } from '../../../../core/server';
import { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search';
+import { AggsSetup, AggsStart } from './aggs';
import { SearchUsage } from './collectors/usage';
import { IEsSearchRequest, IEsSearchResponse } from './es_search';
@@ -31,6 +32,7 @@ export interface ISearchOptions {
}
export interface ISearchSetup {
+ aggs: AggsSetup;
/**
* Extension point exposed for other plugins to register their own search
* strategies.
@@ -44,6 +46,7 @@ export interface ISearchSetup {
}
export interface ISearchStart {
+ aggs: AggsStart;
/**
* Get other registered search strategies. For example, if a new strategy needs to use the
* already-registered ES search strategy, it can use this function to accomplish that.
diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md
index 37d569a4bf9fe..9c8a79f27a9db 100644
--- a/src/plugins/data/server/server.api.md
+++ b/src/plugins/data/server/server.api.md
@@ -4,7 +4,9 @@
```ts
+import { $Values } from '@kbn/utility-types';
import { ApiResponse } from '@elastic/elasticsearch/lib/Transport';
+import { Assign } from '@kbn/utility-types';
import Boom from 'boom';
import { BulkIndexDocumentsParams } from 'elasticsearch';
import { CatAliasesParams } from 'elasticsearch';
@@ -22,7 +24,6 @@ import { CatTasksParams } from 'elasticsearch';
import { CatThreadPoolParams } from 'elasticsearch';
import { ClearScrollParams } from 'elasticsearch';
import { Client } from 'elasticsearch';
-import { ClientOptions } from '@elastic/elasticsearch';
import { ClusterAllocationExplainParams } from 'elasticsearch';
import { ClusterGetSettingsParams } from 'elasticsearch';
import { ClusterHealthParams } from 'elasticsearch';
@@ -31,19 +32,23 @@ import { ClusterPutSettingsParams } from 'elasticsearch';
import { ClusterRerouteParams } from 'elasticsearch';
import { ClusterStateParams } from 'elasticsearch';
import { ClusterStatsParams } from 'elasticsearch';
-import { ConfigOptions } from 'elasticsearch';
+import { CoreSetup } from 'src/core/server';
import { CoreSetup as CoreSetup_2 } from 'kibana/server';
+import { CoreStart } from 'src/core/server';
import { CountParams } from 'elasticsearch';
import { CreateDocumentParams } from 'elasticsearch';
import { DeleteDocumentByQueryParams } from 'elasticsearch';
import { DeleteDocumentParams } from 'elasticsearch';
import { DeleteScriptParams } from 'elasticsearch';
import { DeleteTemplateParams } from 'elasticsearch';
-import { DetailedPeerCertificate } from 'tls';
import { Duration } from 'moment';
+import { Ensure } from '@kbn/utility-types';
import { ErrorToastOptions } from 'src/core/public/notifications';
import { ExistsParams } from 'elasticsearch';
import { ExplainParams } from 'elasticsearch';
+import { ExpressionAstFunction } from 'src/plugins/expressions/common';
+import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
+import { FetchOptions } from 'src/plugins/data/public';
import { FieldStatsParams } from 'elasticsearch';
import { GenericParams } from 'elasticsearch';
import { GetParams } from 'elasticsearch';
@@ -94,13 +99,15 @@ import { IngestDeletePipelineParams } from 'elasticsearch';
import { IngestGetPipelineParams } from 'elasticsearch';
import { IngestPutPipelineParams } from 'elasticsearch';
import { IngestSimulateParams } from 'elasticsearch';
+import { ISearchSource } from 'src/plugins/data/public';
import { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import { KibanaConfigType as KibanaConfigType_2 } from 'src/core/server/kibana_config';
-import { KibanaRequest as KibanaRequest_2 } from 'kibana/server';
+import { KibanaRequest } from 'kibana/server';
import { LegacyAPICaller as LegacyAPICaller_2 } from 'kibana/server';
import { Logger as Logger_2 } from 'kibana/server';
import { MGetParams } from 'elasticsearch';
import { MGetResponse } from 'elasticsearch';
+import { Moment } from 'moment';
import moment from 'moment';
import { MSearchParams } from 'elasticsearch';
import { MSearchResponse } from 'elasticsearch';
@@ -109,27 +116,26 @@ import { MTermVectorsParams } from 'elasticsearch';
import { NodesHotThreadsParams } from 'elasticsearch';
import { NodesInfoParams } from 'elasticsearch';
import { NodesStatsParams } from 'elasticsearch';
-import { ObjectType } from '@kbn/config-schema';
import { Observable } from 'rxjs';
-import { PeerCertificate } from 'tls';
import { PingParams } from 'elasticsearch';
+import { Plugin as Plugin_2 } from 'src/core/server';
+import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/server';
import { PutScriptParams } from 'elasticsearch';
import { PutTemplateParams } from 'elasticsearch';
import { RecursiveReadonly } from '@kbn/utility-types';
import { ReindexParams } from 'elasticsearch';
import { ReindexRethrottleParams } from 'elasticsearch';
import { RenderSearchTemplateParams } from 'elasticsearch';
-import { Request } from 'hapi';
-import { ResponseObject } from 'hapi';
-import { ResponseToolkit } from 'hapi';
-import { SavedObject as SavedObject_2 } from 'src/core/server';
-import { SchemaTypeError } from '@kbn/config-schema';
+import { RequestAdapter } from 'src/plugins/inspector/common';
+import { RequestStatistics } from 'src/plugins/inspector/common';
+import { SavedObject } from 'src/core/server';
+import { SavedObjectsClientContract as SavedObjectsClientContract_2 } from 'src/core/server';
import { ScrollParams } from 'elasticsearch';
import { SearchParams } from 'elasticsearch';
import { SearchResponse } from 'elasticsearch';
import { SearchShardsParams } from 'elasticsearch';
import { SearchTemplateParams } from 'elasticsearch';
-import { ShallowPromise } from '@kbn/utility-types';
+import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common';
import { ShardsResponse } from 'elasticsearch';
import { SnapshotCreateParams } from 'elasticsearch';
import { SnapshotCreateRepositoryParams } from 'elasticsearch';
@@ -140,7 +146,6 @@ import { SnapshotGetRepositoryParams } from 'elasticsearch';
import { SnapshotRestoreParams } from 'elasticsearch';
import { SnapshotStatusParams } from 'elasticsearch';
import { SnapshotVerifyRepositoryParams } from 'elasticsearch';
-import { Stream } from 'stream';
import { SuggestParams } from 'elasticsearch';
import { TasksCancelParams } from 'elasticsearch';
import { TasksGetParams } from 'elasticsearch';
@@ -156,7 +161,96 @@ import { Unit } from '@elastic/datemath';
import { UnwrapPromiseOrReturn } from '@kbn/utility-types';
import { UpdateDocumentByQueryParams } from 'elasticsearch';
import { UpdateDocumentParams } from 'elasticsearch';
-import { Url } from 'url';
+
+// Warning: (ae-forgotten-export) The symbol "AggConfigSerialized" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "AggConfigOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type AggConfigOptions = Assign;
+
+// Warning: (ae-missing-release-tag) "AggGroupLabels" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export const AggGroupLabels: {
+ buckets: string;
+ metrics: string;
+ none: string;
+};
+
+// Warning: (ae-missing-release-tag) "AggGroupName" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type AggGroupName = $Values;
+
+// Warning: (ae-missing-release-tag) "AggGroupNames" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export const AggGroupNames: Readonly<{
+ Buckets: "buckets";
+ Metrics: "metrics";
+ None: "none";
+}>;
+
+// Warning: (ae-forgotten-export) The symbol "BaseParamType" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "AggParam" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type AggParam = BaseParamType;
+
+// Warning: (ae-missing-release-tag) "AggParamOption" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export interface AggParamOption {
+ // (undocumented)
+ display: string;
+ // Warning: (ae-forgotten-export) The symbol "AggConfig" needs to be exported by the entry point index.d.ts
+ //
+ // (undocumented)
+ enabled?(agg: AggConfig): boolean;
+ // (undocumented)
+ val: string;
+}
+
+// Warning: (ae-missing-release-tag) "AggParamType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export class AggParamType extends BaseParamType {
+ constructor(config: Record);
+ // (undocumented)
+ allowedAggs: string[];
+ // (undocumented)
+ makeAgg: (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig;
+}
+
+// Warning: (ae-missing-release-tag) "BUCKET_TYPES" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export enum BUCKET_TYPES {
+ // (undocumented)
+ DATE_HISTOGRAM = "date_histogram",
+ // (undocumented)
+ DATE_RANGE = "date_range",
+ // (undocumented)
+ FILTER = "filter",
+ // (undocumented)
+ FILTERS = "filters",
+ // (undocumented)
+ GEOHASH_GRID = "geohash_grid",
+ // (undocumented)
+ GEOTILE_GRID = "geotile_grid",
+ // (undocumented)
+ HISTOGRAM = "histogram",
+ // (undocumented)
+ IP_RANGE = "ip_range",
+ // (undocumented)
+ RANGE = "range",
+ // (undocumented)
+ SIGNIFICANT_TERMS = "significant_terms",
+ // (undocumented)
+ TERMS = "terms"
+}
// Warning: (ae-missing-release-tag) "castEsToKbnFieldTypeName" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -369,6 +463,22 @@ export function getTotalLoaded({ total, failed, successful }: ShardsResponse): {
loaded: number;
};
+// Warning: (ae-missing-release-tag) "IAggConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public
+export type IAggConfig = AggConfig;
+
+// Warning: (ae-forgotten-export) The symbol "AggConfigs" needs to be exported by the entry point index.d.ts
+//
+// @internal
+export type IAggConfigs = AggConfigs;
+
+// Warning: (ae-forgotten-export) The symbol "AggType" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "IAggType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type IAggType = AggType;
+
// Warning: (ae-forgotten-export) The symbol "IKibanaSearchRequest" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "IEsSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -398,6 +508,12 @@ export interface IEsSearchResponse extends IKibanaSearchResponse {
// @public (undocumented)
export type IFieldFormatsRegistry = PublicMethodsOf;
+// Warning: (ae-forgotten-export) The symbol "FieldParamType" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "IFieldParamType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type IFieldParamType = FieldParamType;
+
// Warning: (ae-missing-release-tag) "IFieldSubType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -479,6 +595,12 @@ export interface IIndexPattern {
type?: string;
}
+// Warning: (ae-forgotten-export) The symbol "MetricAggType" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "IMetricAggType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type IMetricAggType = MetricAggType;
+
// Warning: (ae-missing-release-tag) "IndexPatternAttributes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public @deprecated
@@ -561,6 +683,10 @@ export interface ISearchOptions {
//
// @public (undocumented)
export interface ISearchSetup {
+ // Warning: (ae-forgotten-export) The symbol "AggsSetup" needs to be exported by the entry point index.d.ts
+ //
+ // (undocumented)
+ aggs: AggsSetup;
registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void;
usage?: SearchUsage;
}
@@ -569,6 +695,10 @@ export interface ISearchSetup {
//
// @public (undocumented)
export interface ISearchStart {
+ // Warning: (ae-forgotten-export) The symbol "AggsStart" needs to be exported by the entry point index.d.ts
+ //
+ // (undocumented)
+ aggs: AggsStart;
getSearchStrategy: (name: string) => ISearchStrategy;
// Warning: (ae-forgotten-export) The symbol "RequestHandlerContext" needs to be exported by the entry point index.d.ts
//
@@ -632,6 +762,77 @@ export interface KueryNode {
type: keyof NodeTypes;
}
+// Warning: (ae-missing-release-tag) "METRIC_TYPES" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export enum METRIC_TYPES {
+ // (undocumented)
+ AVG = "avg",
+ // (undocumented)
+ AVG_BUCKET = "avg_bucket",
+ // (undocumented)
+ CARDINALITY = "cardinality",
+ // (undocumented)
+ COUNT = "count",
+ // (undocumented)
+ CUMULATIVE_SUM = "cumulative_sum",
+ // (undocumented)
+ DERIVATIVE = "derivative",
+ // (undocumented)
+ GEO_BOUNDS = "geo_bounds",
+ // (undocumented)
+ GEO_CENTROID = "geo_centroid",
+ // (undocumented)
+ MAX = "max",
+ // (undocumented)
+ MAX_BUCKET = "max_bucket",
+ // (undocumented)
+ MEDIAN = "median",
+ // (undocumented)
+ MIN = "min",
+ // (undocumented)
+ MIN_BUCKET = "min_bucket",
+ // (undocumented)
+ MOVING_FN = "moving_avg",
+ // (undocumented)
+ PERCENTILE_RANKS = "percentile_ranks",
+ // (undocumented)
+ PERCENTILES = "percentiles",
+ // (undocumented)
+ SERIAL_DIFF = "serial_diff",
+ // (undocumented)
+ STD_DEV = "std_dev",
+ // (undocumented)
+ SUM = "sum",
+ // (undocumented)
+ SUM_BUCKET = "sum_bucket",
+ // (undocumented)
+ TOP_HITS = "top_hits"
+}
+
+// Warning: (ae-missing-release-tag) "OptionedParamType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export class OptionedParamType extends BaseParamType {
+ constructor(config: Record);
+ // (undocumented)
+ options: OptionedValueProp[];
+}
+
+// Warning: (ae-missing-release-tag) "OptionedValueProp" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export interface OptionedValueProp {
+ // (undocumented)
+ disabled?: boolean;
+ // (undocumented)
+ isCompatible: (agg: IAggConfig) => boolean;
+ // (undocumented)
+ text: string;
+ // (undocumented)
+ value: string;
+}
+
// Warning: (ae-forgotten-export) The symbol "parseEsInterval" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "ParsedInterval" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -643,25 +844,20 @@ export type ParsedInterval = ReturnType;
// @public (undocumented)
export function parseInterval(interval: string): moment.Duration | null;
-// Warning: (ae-forgotten-export) The symbol "Plugin" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "DataPluginSetupDependencies" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "DataPluginStartDependencies" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "DataServerPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
-export class Plugin implements Plugin_2 {
- // Warning: (ae-forgotten-export) The symbol "PluginInitializerContext" needs to be exported by the entry point index.d.ts
- constructor(initializerContext: PluginInitializerContext);
- // Warning: (ae-forgotten-export) The symbol "CoreSetup" needs to be exported by the entry point index.d.ts
- // Warning: (ae-forgotten-export) The symbol "DataPluginSetupDependencies" needs to be exported by the entry point index.d.ts
- //
+export class Plugin implements Plugin_2 {
+ constructor(initializerContext: PluginInitializerContext_2);
// (undocumented)
- setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): {
+ setup(core: CoreSetup, { expressions, usageCollection }: DataPluginSetupDependencies): {
search: ISearchSetup;
fieldFormats: {
- register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number;
+ register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number;
};
};
- // Warning: (ae-forgotten-export) The symbol "CoreStart" needs to be exported by the entry point index.d.ts
- //
// (undocumented)
start(core: CoreStart): {
search: ISearchStart;
@@ -676,6 +872,8 @@ export class Plugin implements Plugin_2 {
stop(): void;
}
+// Warning: (ae-forgotten-export) The symbol "PluginInitializerContext" needs to be exported by the entry point index.d.ts
+//
// @public
export function plugin(initializerContext: PluginInitializerContext): Plugin;
@@ -734,16 +932,36 @@ export interface RefreshInterval {
// @public (undocumented)
export const search: {
aggs: {
+ CidrMask: typeof CidrMask;
dateHistogramInterval: typeof dateHistogramInterval;
+ intervalOptions: ({
+ display: string;
+ val: string;
+ enabled(agg: import("../common").IBucketAggConfig): boolean | "" | undefined;
+ } | {
+ display: string;
+ val: string;
+ })[];
InvalidEsCalendarIntervalError: typeof InvalidEsCalendarIntervalError;
InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError;
Ipv4Address: typeof Ipv4Address;
+ isNumberType: (agg: import("../common").AggConfig) => boolean;
+ isStringType: (agg: import("../common").AggConfig) => boolean;
+ isType: (...types: string[]) => (agg: import("../common").AggConfig) => boolean;
isValidEsInterval: typeof isValidEsInterval;
isValidInterval: typeof isValidInterval;
+ parentPipelineType: string;
parseEsInterval: typeof parseEsInterval;
parseInterval: typeof parseInterval;
+ propFilter: typeof propFilter;
+ siblingPipelineType: string;
+ termsAggFilter: string[];
toAbsoluteDates: typeof toAbsoluteDates;
};
+ getRequestInspectorStats: typeof getRequestInspectorStats;
+ getResponseInspectorStats: typeof getResponseInspectorStats;
+ tabifyAggResponse: typeof tabifyAggResponse;
+ tabifyGetColumns: typeof tabifyGetColumns;
};
// Warning: (ae-missing-release-tag) "SearchUsage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -761,6 +979,27 @@ export interface SearchUsage {
// @public (undocumented)
export function shouldReadFieldFromDocValues(aggregatable: boolean, esType: string): boolean;
+// @public (undocumented)
+export interface TabbedAggColumn {
+ // (undocumented)
+ aggConfig: IAggConfig;
+ // (undocumented)
+ id: string;
+ // (undocumented)
+ name: string;
+}
+
+// @public (undocumented)
+export type TabbedAggRow = Record;
+
+// @public (undocumented)
+export interface TabbedTable {
+ // (undocumented)
+ columns: TabbedAggColumn[];
+ // (undocumented)
+ rows: TabbedAggRow[];
+}
+
// Warning: (ae-missing-release-tag) "TimeRange" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -836,13 +1075,19 @@ export function usageProvider(core: CoreSetup_2): SearchUsage;
// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:189:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:190:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:193:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:221:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:221:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:221:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:221:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:223:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:224:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:233:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:234:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:235:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:239:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:240:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:244:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:247:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)
diff --git a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx
index 8976f8ea3c107..ab7adba193d87 100644
--- a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx
+++ b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx
@@ -20,7 +20,6 @@
import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { ActionBar, ActionBarProps } from './action_bar';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE } from '../../query_parameters/constants';
diff --git a/src/plugins/discover/public/application/angular/doc.html b/src/plugins/discover/public/application/angular/doc.html
index fc1bff7eef6ec..dcd5760eff155 100644
--- a/src/plugins/discover/public/application/angular/doc.html
+++ b/src/plugins/discover/public/application/angular/doc.html
@@ -1,6 +1,5 @@
{
.when('/doc/:indexPattern/:index', {
// have to be written as function expression, because it's not compiled in dev mode
// eslint-disable-next-line object-shorthand
- controller: function ($scope: LazyScope, $route: any, es: any) {
+ controller: function ($scope: LazyScope, $route: any) {
timefilter.disableAutoRefreshSelector();
timefilter.disableTimeRangeSelector();
- $scope.esClient = es;
$scope.id = $route.current.params.id;
$scope.index = $route.current.params.index;
$scope.indexPatternId = $route.current.params.indexPattern;
diff --git a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx
index 524161c77cbf8..2cd829d89f78e 100644
--- a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx
+++ b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx
@@ -20,7 +20,6 @@
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { ToolBarPagerButtons } from './tool_bar_pager_buttons';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
test('it renders ToolBarPagerButtons', () => {
diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx
index b201bea26503e..224e249a274cd 100644
--- a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx
+++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx
@@ -20,7 +20,6 @@
import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { TableHeader } from './table_header';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { SortOrder } from './helpers';
import { IndexPattern, IFieldType } from '../../../../../kibana_services';
diff --git a/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx b/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx
index 1c9439bc34e58..1cc8247957512 100644
--- a/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx
+++ b/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx
@@ -22,7 +22,6 @@ import { ReactWrapper } from 'enzyme';
import { ContextErrorMessage } from './context_error_message';
// @ts-ignore
import { FAILURE_REASONS, LOADING_STATUS } from '../../angular/context/query';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
describe('loading spinner', function () {
diff --git a/src/plugins/discover/public/application/components/doc/doc.test.tsx b/src/plugins/discover/public/application/components/doc/doc.test.tsx
index 0bc621714c70f..d562291db46ac 100644
--- a/src/plugins/discover/public/application/components/doc/doc.test.tsx
+++ b/src/plugins/discover/public/application/components/doc/doc.test.tsx
@@ -16,14 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { throwError, of } from 'rxjs';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { ReactWrapper } from 'enzyme';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { Doc, DocProps } from './doc';
+const mockSearchApi = jest.fn();
+
jest.mock('../../../kibana_services', () => {
let registry: any[] = [];
@@ -32,6 +34,11 @@ jest.mock('../../../kibana_services', () => {
metadata: {
branch: 'test',
},
+ data: {
+ search: {
+ search: mockSearchApi,
+ },
+ },
}),
getDocViewsRegistry: () => ({
addDocView(view: any) {
@@ -60,7 +67,7 @@ const waitForPromises = async () =>
* this works but logs ugly error messages until we're using React 16.9
* should be adapted when we upgrade
*/
-async function mountDoc(search: () => void, update = false, indexPatternGetter: any = null) {
+async function mountDoc(update = false, indexPatternGetter: any = null) {
const indexPattern = {
getComputedFields: () => [],
};
@@ -71,7 +78,6 @@ async function mountDoc(search: () => void, update = false, indexPatternGetter:
const props = {
id: '1',
index: 'index1',
- esClient: { search } as any,
indexPatternId: 'xyz',
indexPatternService,
} as DocProps;
@@ -89,32 +95,33 @@ async function mountDoc(search: () => void, update = false, indexPatternGetter:
describe('Test of of Discover', () => {
test('renders loading msg', async () => {
- const comp = await mountDoc(jest.fn());
+ const comp = await mountDoc();
expect(findTestSubject(comp, 'doc-msg-loading').length).toBe(1);
});
test('renders IndexPattern notFound msg', async () => {
const indexPatternGetter = jest.fn(() => Promise.reject({ savedObjectId: '007' }));
- const comp = await mountDoc(jest.fn(), true, indexPatternGetter);
+ const comp = await mountDoc(true, indexPatternGetter);
expect(findTestSubject(comp, 'doc-msg-notFoundIndexPattern').length).toBe(1);
});
test('renders notFound msg', async () => {
- const search = jest.fn(() => Promise.reject({ status: 404 }));
- const comp = await mountDoc(search, true);
+ mockSearchApi.mockImplementation(() => throwError({ status: 404 }));
+ const comp = await mountDoc(true);
expect(findTestSubject(comp, 'doc-msg-notFound').length).toBe(1);
});
test('renders error msg', async () => {
- const search = jest.fn(() => Promise.reject('whatever'));
- const comp = await mountDoc(search, true);
+ mockSearchApi.mockImplementation(() => throwError({ error: 'something else' }));
+ const comp = await mountDoc(true);
expect(findTestSubject(comp, 'doc-msg-error').length).toBe(1);
});
test('renders elasticsearch hit ', async () => {
- const hit = { hits: { total: 1, hits: [{ _id: 1, _source: { test: 1 } }] } };
- const search = jest.fn(() => Promise.resolve(hit));
- const comp = await mountDoc(search, true);
+ mockSearchApi.mockImplementation(() =>
+ of({ rawResponse: { hits: { total: 1, hits: [{ _id: 1, _source: { test: 1 } }] } } })
+ );
+ const comp = await mountDoc(true);
expect(findTestSubject(comp, 'doc-hit').length).toBe(1);
});
});
diff --git a/src/plugins/discover/public/application/components/doc/doc.tsx b/src/plugins/discover/public/application/components/doc/doc.tsx
index 0e31ded267b75..2623b5a270a31 100644
--- a/src/plugins/discover/public/application/components/doc/doc.tsx
+++ b/src/plugins/discover/public/application/components/doc/doc.tsx
@@ -23,17 +23,6 @@ import { IndexPatternsContract } from 'src/plugins/data/public';
import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search';
import { getServices } from '../../../kibana_services';
import { DocViewer } from '../doc_viewer/doc_viewer';
-import { ElasticSearchHit } from '../../doc_views/doc_views_types';
-
-export interface ElasticSearchResult {
- hits: {
- hits: [ElasticSearchHit];
- max_score: number;
- };
- timed_out: boolean;
- took: number;
- shards: Record;
-}
export interface DocProps {
/**
@@ -53,12 +42,6 @@ export interface DocProps {
* IndexPatternService to get a given index pattern by ID
*/
indexPatternService: IndexPatternsContract;
- /**
- * Client of ElasticSearch to use for the query
- */
- esClient: {
- search: (payload: { index: string; body: Record }) => Promise;
- };
}
export function Doc(props: DocProps) {
diff --git a/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx b/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx
index a8fe8de833315..e0d505f9aa6fa 100644
--- a/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx
+++ b/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx
@@ -19,6 +19,21 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { buildSearchBody, useEsDocSearch, ElasticRequestState } from './use_es_doc_search';
import { DocProps } from './doc';
+import { Observable } from 'rxjs';
+
+const mockSearchResult = new Observable();
+
+jest.mock('../../../kibana_services', () => ({
+ getServices: () => ({
+ data: {
+ search: {
+ search: jest.fn(() => {
+ return mockSearchResult;
+ }),
+ },
+ },
+ }),
+}));
describe('Test of helper / hook', () => {
test('buildSearchBody', () => {
@@ -53,7 +68,6 @@ describe('Test of helper / hook', () => {
const props = {
id: '1',
index: 'index1',
- esClient: { search: jest.fn(() => new Promise(() => {})) },
indexPatternId: 'xyz',
indexPatternService,
} as DocProps;
diff --git a/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts b/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts
index 00496a3a72681..522ebad1691a9 100644
--- a/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts
+++ b/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts
@@ -17,7 +17,7 @@
* under the License.
*/
import { useEffect, useState } from 'react';
-import { IndexPattern } from '../../../kibana_services';
+import { IndexPattern, getServices } from '../../../kibana_services';
import { DocProps } from './doc';
import { ElasticSearchHit } from '../../doc_views/doc_views_types';
@@ -53,7 +53,6 @@ export function buildSearchBody(id: string, indexPattern: IndexPattern): Record<
* Custom react hook for querying a single doc in ElasticSearch
*/
export function useEsDocSearch({
- esClient,
id,
index,
indexPatternId,
@@ -69,12 +68,18 @@ export function useEsDocSearch({
const indexPatternEntity = await indexPatternService.get(indexPatternId);
setIndexPattern(indexPatternEntity);
- const { hits } = await esClient.search({
- index,
- body: buildSearchBody(id, indexPatternEntity),
- });
+ const { rawResponse } = await getServices()
+ .data.search.search({
+ params: {
+ index,
+ body: buildSearchBody(id, indexPatternEntity),
+ },
+ })
+ .toPromise();
- if (hits && hits.hits && hits.hits[0]) {
+ const hits = rawResponse.hits;
+
+ if (hits?.hits?.[0]) {
setStatus(ElasticRequestState.Found);
setHit(hits.hits[0]);
} else {
@@ -91,6 +96,6 @@ export function useEsDocSearch({
}
}
requestData();
- }, [esClient, id, index, indexPatternId, indexPatternService]);
+ }, [id, index, indexPatternId, indexPatternService]);
return [status, hit, indexPattern];
}
diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx
index 3710ce72533db..9115915690324 100644
--- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx
+++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx
@@ -19,7 +19,6 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import { DocViewer } from './doc_viewer';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { getDocViewsRegistry } from '../../../kibana_services';
import { DocViewRenderProps } from '../../doc_views/doc_views_types';
diff --git a/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx b/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx
index 84ad19dbddcbf..c2eb4f08cf549 100644
--- a/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx
+++ b/src/plugins/discover/public/application/components/hits_counter/hits_counter.test.tsx
@@ -20,7 +20,6 @@ import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { ReactWrapper } from 'enzyme';
import { HitsCounter, HitsCounterProps } from './hits_counter';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
describe('hits counter', function () {
diff --git a/src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap b/src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap
index ee88ce6088d7e..d6f48a9b3c774 100644
--- a/src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap
+++ b/src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap
@@ -9,6 +9,9 @@ exports[`returns the \`JsonCodeEditor\` component 1`] = `
>
{
"_index": "test",
+ "_type": "doc",
+ "_id": "foo",
+ "_score": 1,
"_source": {
"test": 123
}
diff --git a/src/plugins/discover/public/application/components/json_code_block/json_code_block.test.tsx b/src/plugins/discover/public/application/components/json_code_block/json_code_block.test.tsx
index 3cbcab5036251..a737b3954ceea 100644
--- a/src/plugins/discover/public/application/components/json_code_block/json_code_block.test.tsx
+++ b/src/plugins/discover/public/application/components/json_code_block/json_code_block.test.tsx
@@ -23,7 +23,7 @@ import { IndexPattern } from '../../../../../data/public';
it('returns the `JsonCodeEditor` component', () => {
const props = {
- hit: { _index: 'test', _source: { test: 123 } },
+ hit: { _index: 'test', _type: 'doc', _id: 'foo', _score: 1, _source: { test: 123 } },
columns: [],
indexPattern: {} as IndexPattern,
filter: jest.fn(),
diff --git a/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx b/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx
index 3321ac764a05b..e996da5fe0e3c 100644
--- a/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx
+++ b/src/plugins/discover/public/application/components/loading_spinner/loading_spinner.test.tsx
@@ -20,7 +20,6 @@ import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { ReactWrapper } from 'enzyme';
import { LoadingSpinner } from './loading_spinner';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
describe('loading spinner', function () {
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.scss b/src/plugins/discover/public/application/components/sidebar/discover_field.scss
new file mode 100644
index 0000000000000..8e1dd41f66ab1
--- /dev/null
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field.scss
@@ -0,0 +1,4 @@
+.dscSidebarItem__fieldPopoverPanel {
+ min-width: 260px;
+ max-width: 300px;
+}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx
index 3f12a8c0fa769..a0d9e3c541e47 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx
@@ -18,7 +18,6 @@
*/
import React from 'react';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
// @ts-ignore
import StubIndexPattern from 'test_utils/stub_index_pattern';
@@ -105,9 +104,4 @@ describe('discover sidebar field', function () {
findTestSubject(comp, 'fieldToggle-bytes').simulate('click');
expect(props.onRemoveField).toHaveBeenCalledWith('bytes');
});
- it('should trigger onShowDetails', function () {
- const { comp, props } = getComponent();
- findTestSubject(comp, 'field-bytes-showDetails').simulate('click');
- expect(props.onShowDetails).toHaveBeenCalledWith(true, props.field);
- });
});
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
index 724908281146d..639dbfe09277c 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
@@ -16,15 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React from 'react';
-import { EuiButton } from '@elastic/eui';
+import React, { useState } from 'react';
+import { EuiPopover, EuiPopoverTitle, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { DiscoverFieldDetails } from './discover_field_details';
-import { FieldIcon } from '../../../../../kibana_react/public';
+import { FieldIcon, FieldButton } from '../../../../../kibana_react/public';
import { FieldDetails } from './types';
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
import { shortenDottedString } from '../../helpers';
import { getFieldTypeName } from './lib/get_field_type_name';
+import './discover_field.scss';
export interface DiscoverFieldProps {
/**
@@ -48,14 +49,6 @@ export interface DiscoverFieldProps {
* @param fieldName
*/
onRemoveField: (fieldName: string) => void;
- /**
- * Callback to hide/show details, buckets of the field
- */
- onShowDetails: (show: boolean, field: IndexPatternField) => void;
- /**
- * Determines, whether details of the field are displayed
- */
- showDetails: boolean;
/**
* Retrieve details data for the field
*/
@@ -76,22 +69,14 @@ export function DiscoverField({
onAddField,
onRemoveField,
onAddFilter,
- onShowDetails,
- showDetails,
getDetails,
selected,
useShortDots,
}: DiscoverFieldProps) {
- const addLabel = i18n.translate('discover.fieldChooser.discoverField.addButtonLabel', {
- defaultMessage: 'Add',
- });
const addLabelAria = i18n.translate('discover.fieldChooser.discoverField.addButtonAriaLabel', {
defaultMessage: 'Add {field} to table',
values: { field: field.name },
});
- const removeLabel = i18n.translate('discover.fieldChooser.discoverField.removeButtonLabel', {
- defaultMessage: 'Remove',
- });
const removeLabelAria = i18n.translate(
'discover.fieldChooser.discoverField.removeButtonAriaLabel',
{
@@ -100,6 +85,8 @@ export function DiscoverField({
}
);
+ const [infoIsOpen, setOpen] = useState(false);
+
const toggleDisplay = (f: IndexPatternField) => {
if (selected) {
onRemoveField(f.name);
@@ -108,6 +95,10 @@ export function DiscoverField({
}
};
+ function togglePopover() {
+ setOpen(!infoIsOpen);
+ }
+
function wrapOnDot(str?: string) {
// u200B is a non-width white-space character, which allows
// the browser to efficiently word-wrap right after the dot
@@ -115,64 +106,96 @@ export function DiscoverField({
return str ? str.replace(/\./g, '.\u200B') : '';
}
- return (
- <>
- onShowDetails(!showDetails, field)}
- onKeyPress={() => onShowDetails(!showDetails, field)}
- data-test-subj={`field-${field.name}-showDetails`}
+ const dscFieldIcon = (
+
+ );
+
+ const fieldName = (
+
+ {useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)}
+
+ );
+
+ let actionButton;
+ if (field.name !== '_source' && !selected) {
+ actionButton = (
+
+ ) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ toggleDisplay(field);
+ }}
+ data-test-subj={`fieldToggle-${field.name}`}
+ aria-label={addLabelAria}
+ />
+
+ );
+ } else if (field.name !== '_source' && selected) {
+ actionButton = (
+
-
-
-
-
- {useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)}
-
-
- {field.name !== '_source' && !selected && (
- ) => {
- ev.preventDefault();
- ev.stopPropagation();
- toggleDisplay(field);
- }}
- data-test-subj={`fieldToggle-${field.name}`}
- arial-label={addLabelAria}
- >
- {addLabel}
-
- )}
- {field.name !== '_source' && selected && (
- ) => {
- ev.preventDefault();
- ev.stopPropagation();
- toggleDisplay(field);
- }}
- data-test-subj={`fieldToggle-${field.name}`}
- arial-label={removeLabelAria}
- >
- {removeLabel}
-
- )}
-
-
- {showDetails && (
+ ) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ toggleDisplay(field);
+ }}
+ data-test-subj={`fieldToggle-${field.name}`}
+ aria-label={removeLabelAria}
+ />
+
+ );
+ }
+
+ return (
+ {
+ togglePopover();
+ }}
+ buttonProps={{ 'data-test-subj': `field-${field.name}-showDetails` }}
+ fieldIcon={dscFieldIcon}
+ fieldAction={actionButton}
+ fieldName={fieldName}
+ />
+ }
+ isOpen={infoIsOpen}
+ closePopover={() => setOpen(false)}
+ anchorPosition="rightUp"
+ panelClassName="dscSidebarItem__fieldPopoverPanel"
+ >
+
+ {' '}
+ {i18n.translate('discover.fieldChooser.discoverField.fieldTopValuesLabel', {
+ defaultMessage: 'Top 5 values',
+ })}
+
+ {infoIsOpen && (
)}
- >
+
);
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.scss b/src/plugins/discover/public/application/components/sidebar/discover_field_details.scss
new file mode 100644
index 0000000000000..f4b3eed741f9f
--- /dev/null
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.scss
@@ -0,0 +1,5 @@
+.dscFieldDetails__visualizeBtn {
+ @include euiFontSizeXS;
+ height: $euiSizeL !important;
+ min-width: $euiSize * 4;
+}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
index dd95a45f71626..3061839bf3ef0 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
@@ -16,14 +16,19 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React from 'react';
-import { EuiLink, EuiIconTip, EuiText } from '@elastic/eui';
+import React, { useState, useEffect } from 'react';
+import { EuiLink, EuiIconTip, EuiText, EuiPopoverFooter, EuiButton, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { DiscoverFieldBucket } from './discover_field_bucket';
import { getWarnings } from './lib/get_warnings';
+import {
+ triggerVisualizeActions,
+ isFieldVisualizable,
+ getVisualizeHref,
+} from './lib/visualize_trigger_utils';
import { Bucket, FieldDetails } from './types';
-import { getServices } from '../../../kibana_services';
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
+import './discover_field_details.scss';
interface DiscoverFieldDetailsProps {
field: IndexPatternField;
@@ -39,64 +44,96 @@ export function DiscoverFieldDetails({
onAddFilter,
}: DiscoverFieldDetailsProps) {
const warnings = getWarnings(field);
+ const [showVisualizeLink, setShowVisualizeLink] = useState(false);
+ const [visualizeLink, setVisualizeLink] = useState('');
+
+ useEffect(() => {
+ isFieldVisualizable(field, indexPattern.id, details.columns).then(
+ (flag) => {
+ setShowVisualizeLink(flag);
+ // get href only if Visualize button is enabled
+ getVisualizeHref(field, indexPattern.id, details.columns).then(
+ (uri) => {
+ if (uri) setVisualizeLink(uri);
+ },
+ () => {
+ setVisualizeLink('');
+ }
+ );
+ },
+ () => {
+ setShowVisualizeLink(false);
+ }
+ );
+ }, [field, indexPattern.id, details.columns]);
+
+ const handleVisualizeLinkClick = (event: React.MouseEvent) => {
+ // regular link click. let the uiActions code handle the navigation and show popup if needed
+ event.preventDefault();
+ triggerVisualizeActions(field, indexPattern.id, details.columns);
+ };
return (
-
- {!details.error && (
-
- {' '}
- {!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
- onAddFilter('_exists_', field.name, '+')}>
- {details.exists}
-
- ) : (
- {details.exists}
- )}{' '}
- / {details.total}{' '}
-
-
- )}
- {details.error &&
{details.error} }
- {!details.error && (
-
- {details.buckets.map((bucket: Bucket, idx: number) => (
-
- ))}
-
- )}
+ <>
+
+ {details.error &&
{details.error} }
+ {!details.error && (
+
+ {details.buckets.map((bucket: Bucket, idx: number) => (
+
+ ))}
+
+ )}
- {details.visualizeUrl && (
- <>
-
{
- getServices().core.application.navigateToApp(details.visualizeUrl.app, {
- path: details.visualizeUrl.path,
- });
- }}
- className="kuiButton kuiButton--secondary kuiButton--small kuiVerticalRhythmSmall"
- data-test-subj={`fieldVisualize-${field.name}`}
- >
-
+ {showVisualizeLink && (
+ <>
+
+ {/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
+ handleVisualizeLinkClick(e)}
+ href={visualizeLink}
+ size="s"
+ className="dscFieldDetails__visualizeBtn"
+ data-test-subj={`fieldVisualize-${field.name}`}
+ >
+
+
{warnings.length > 0 && (
)}
-
- >
+ >
+ )}
+
+ {!details.error && (
+
+
+ {!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
+ onAddFilter('_exists_', field.name, '+')}>
+ {' '}
+ {details.exists}
+
+ ) : (
+ {details.exists}
+ )}{' '}
+ / {details.total}{' '}
+
+
+
)}
-
+ >
);
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx
index 654df5bfa9ee9..625d6833406eb 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx
@@ -19,7 +19,6 @@
import React, { EventHandler, MouseEvent as ReactMouseEvent } from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { DiscoverFieldSearch, Props } from './discover_field_search';
import { EuiButtonGroupProps, EuiPopover } from '@elastic/eui';
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx
index 24e6f5993a0a5..a1b231c2d4479 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern.test.tsx
@@ -24,7 +24,7 @@ import { ShallowWrapper } from 'enzyme';
import { ChangeIndexPattern } from './change_indexpattern';
import { SavedObject } from 'kibana/server';
import { DiscoverIndexPattern } from './discover_index_pattern';
-import { EuiSelectable, EuiSelectableList } from '@elastic/eui';
+import { EuiSelectable } from '@elastic/eui';
import { IIndexPattern } from 'src/plugins/data/public';
const indexPattern = {
@@ -57,7 +57,7 @@ function getIndexPatternPickerList(instance: ShallowWrapper) {
}
function getIndexPatternPickerOptions(instance: ShallowWrapper) {
- return getIndexPatternPickerList(instance).dive().find(EuiSelectableList).prop('options');
+ return getIndexPatternPickerList(instance).prop('options');
}
function selectIndexPatternPickerOption(instance: ShallowWrapper, selectedLabel: string) {
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss
index 07efd64752c84..f130b0399f467 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss
@@ -42,54 +42,15 @@
}
.dscSidebarItem {
- border-top: 1px solid transparent;
- position: relative;
- display: flex;
- align-items: center;
- justify-content: space-between;
- cursor: pointer;
- font-size: $euiFontSizeXS;
- border-top: solid 1px transparent;
- border-bottom: solid 1px transparent;
- line-height: normal;
- margin-bottom: $euiSizeXS * 0.5;
-
&:hover,
- &:focus {
+ &:focus-within,
+ &[class*='-isActive'] {
.dscSidebarItem__action {
opacity: 1;
}
}
}
-.dscSidebarItem--active {
- border-top: 1px solid $euiColorLightShade;
- color: $euiColorFullShade;
-}
-
-.dscSidebarField {
- padding: $euiSizeXS;
- display: flex;
- align-items: center;
- max-width: 100%;
- width: 100%;
- border: none;
- border-radius: $euiBorderRadius - 1px;
- text-align: left;
-}
-
-.dscSidebarField__name {
- margin-left: $euiSizeS;
- flex-grow: 1;
- word-break: break-word;
- padding-right: 1px;
-}
-
-.dscSidebarField__fieldIcon {
- margin-top: $euiSizeXS / 2;
- margin-right: $euiSizeXS / 2;
-}
-
/**
* 1. Only visually hide the action, so that it's still accessible to screen readers.
* 2. When tabbed to, this element needs to be visible for keyboard accessibility.
@@ -101,7 +62,7 @@
&:focus {
opacity: 1; /* 2 */
}
- font-size: 12px;
+ font-size: $euiFontSizeXS;
padding: 2px 6px !important;
height: 22px !important;
min-width: auto !important;
@@ -130,8 +91,6 @@
}
.dscFieldDetails {
- padding: $euiSizeS;
- background-color: $euiColorLightestShade;
color: $euiTextColor;
margin-bottom: $euiSizeS;
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx
index 90ade60d2073d..9572f35641d69 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx
@@ -19,7 +19,6 @@
import _ from 'lodash';
import { ReactWrapper } from 'enzyme';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
// @ts-ignore
import StubIndexPattern from 'test_utils/stub_index_pattern';
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
index 58b468762c501..1f27766a1756d 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
@@ -92,7 +92,6 @@ export function DiscoverSidebar({
setIndexPattern,
state,
}: DiscoverSidebarProps) {
- const [openFieldMap, setOpenFieldMap] = useState(new Map());
const [showFields, setShowFields] = useState(false);
const [fields, setFields] = useState(null);
const [fieldFilterState, setFieldFilterState] = useState(getDefaultFieldFilter());
@@ -103,19 +102,6 @@ export function DiscoverSidebar({
setFields(newFields);
}, [selectedIndexPattern, fieldCounts, hits, services]);
- const onShowDetails = useCallback(
- (show: boolean, field: IndexPatternField) => {
- if (!show) {
- setOpenFieldMap(new Map(openFieldMap.set(field.name, false)));
- } else {
- setOpenFieldMap(new Map(openFieldMap.set(field.name, true)));
- if (services.capabilities.discover.save) {
- selectedIndexPattern.popularizeField(field.name, 1);
- }
- }
- },
- [openFieldMap, selectedIndexPattern, services.capabilities.discover.save]
- );
const onChangeFieldSearch = useCallback(
(field: string, value: string | boolean | undefined) => {
const newState = setFieldFilterProp(fieldFilterState, field, value);
@@ -125,9 +111,8 @@ export function DiscoverSidebar({
);
const getDetailsByField = useCallback(
- (ipField: IndexPatternField) =>
- getDetails(ipField, selectedIndexPattern, state, columns, hits, services),
- [selectedIndexPattern, state, columns, hits, services]
+ (ipField: IndexPatternField) => getDetails(ipField, hits, columns),
+ [hits, columns]
);
const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING);
@@ -213,9 +198,7 @@ export function DiscoverSidebar({
onAddField={onAddField}
onRemoveField={onRemoveField}
onAddFilter={onAddFilter}
- onShowDetails={onShowDetails}
getDetails={getDetailsByField}
- showDetails={openFieldMap.get(field.name) || false}
selected={true}
useShortDots={useShortDots}
/>
@@ -290,9 +273,7 @@ export function DiscoverSidebar({
onAddField={onAddField}
onRemoveField={onRemoveField}
onAddFilter={onAddFilter}
- onShowDetails={onShowDetails}
getDetails={getDetailsByField}
- showDetails={openFieldMap.get(field.name) || false}
useShortDots={useShortDots}
/>
@@ -318,9 +299,7 @@ export function DiscoverSidebar({
onAddField={onAddField}
onRemoveField={onRemoveField}
onAddFilter={onAddFilter}
- onShowDetails={onShowDetails}
getDetails={getDetailsByField}
- showDetails={openFieldMap.get(field.name) || false}
useShortDots={useShortDots}
/>
diff --git a/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts b/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts
index 7ac9f009d73d5..41d3393672474 100644
--- a/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts
+++ b/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts
@@ -16,32 +16,24 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { getVisualizeUrl, isFieldVisualizable } from './visualize_url_utils';
-import { AppState } from '../../../angular/discover_state';
+
// @ts-ignore
import { fieldCalculator } from './field_calculator';
-import { IndexPatternField, IndexPattern } from '../../../../../../data/public';
-import { DiscoverServices } from '../../../../build_services';
+import { IndexPatternField } from '../../../../../../data/public';
export function getDetails(
field: IndexPatternField,
- indexPattern: IndexPattern,
- state: AppState,
- columns: string[],
hits: Array>,
- services: DiscoverServices
+ columns: string[]
) {
const details = {
- visualizeUrl:
- services.capabilities.visualize.show && isFieldVisualizable(field, services.visualizations)
- ? getVisualizeUrl(field, indexPattern, state, columns, services)
- : null,
...fieldCalculator.getFieldValueCounts({
hits,
field,
count: 5,
grouped: false,
}),
+ columns,
};
if (details.buckets) {
for (const bucket of details.buckets) {
diff --git a/src/plugins/discover/public/application/components/sidebar/lib/visualize_trigger_utils.ts b/src/plugins/discover/public/application/components/sidebar/lib/visualize_trigger_utils.ts
new file mode 100644
index 0000000000000..f058c198cae7f
--- /dev/null
+++ b/src/plugins/discover/public/application/components/sidebar/lib/visualize_trigger_utils.ts
@@ -0,0 +1,110 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+ VISUALIZE_FIELD_TRIGGER,
+ VISUALIZE_GEO_FIELD_TRIGGER,
+ visualizeFieldTrigger,
+ visualizeGeoFieldTrigger,
+} from '../../../../../../ui_actions/public';
+import { getUiActions } from '../../../../kibana_services';
+import { IndexPatternField, KBN_FIELD_TYPES } from '../../../../../../data/public';
+
+function getTriggerConstant(type: string) {
+ return type === KBN_FIELD_TYPES.GEO_POINT || type === KBN_FIELD_TYPES.GEO_SHAPE
+ ? VISUALIZE_GEO_FIELD_TRIGGER
+ : VISUALIZE_FIELD_TRIGGER;
+}
+
+function getTrigger(type: string) {
+ return type === KBN_FIELD_TYPES.GEO_POINT || type === KBN_FIELD_TYPES.GEO_SHAPE
+ ? visualizeGeoFieldTrigger
+ : visualizeFieldTrigger;
+}
+
+async function getCompatibleActions(
+ fieldName: string,
+ indexPatternId: string,
+ contextualFields: string[],
+ trigger: typeof VISUALIZE_FIELD_TRIGGER | typeof VISUALIZE_GEO_FIELD_TRIGGER
+) {
+ const compatibleActions = await getUiActions().getTriggerCompatibleActions(trigger, {
+ indexPatternId,
+ fieldName,
+ contextualFields,
+ });
+ return compatibleActions;
+}
+
+export async function getVisualizeHref(
+ field: IndexPatternField,
+ indexPatternId: string | undefined,
+ contextualFields: string[]
+) {
+ if (!indexPatternId) return undefined;
+ const triggerOptions = {
+ indexPatternId,
+ fieldName: field.name,
+ contextualFields,
+ trigger: getTrigger(field.type),
+ };
+ const compatibleActions = await getCompatibleActions(
+ field.name,
+ indexPatternId,
+ contextualFields,
+ getTriggerConstant(field.type)
+ );
+ // enable the link only if only one action is registered
+ return compatibleActions.length === 1
+ ? compatibleActions[0].getHref?.(triggerOptions)
+ : undefined;
+}
+
+export function triggerVisualizeActions(
+ field: IndexPatternField,
+ indexPatternId: string | undefined,
+ contextualFields: string[]
+) {
+ if (!indexPatternId) return;
+ const trigger = getTriggerConstant(field.type);
+ const triggerOptions = {
+ indexPatternId,
+ fieldName: field.name,
+ contextualFields,
+ };
+ getUiActions().getTrigger(trigger).exec(triggerOptions);
+}
+
+export async function isFieldVisualizable(
+ field: IndexPatternField,
+ indexPatternId: string | undefined,
+ contextualFields: string[]
+) {
+ if (field.name === '_id' || !indexPatternId) {
+ // for first condition you'd get a 'Fielddata access on the _id field is disallowed' error on ES side.
+ return false;
+ }
+ const trigger = getTriggerConstant(field.type);
+ const compatibleActions = await getCompatibleActions(
+ field.name,
+ indexPatternId,
+ contextualFields,
+ trigger
+ );
+ return compatibleActions.length > 0 && field.visualizable;
+}
diff --git a/src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts b/src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts
deleted file mode 100644
index 0c1a44d7845cf..0000000000000
--- a/src/plugins/discover/public/application/components/sidebar/lib/visualize_url_utils.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import uuid from 'uuid/v4';
-import rison from 'rison-node';
-import { parse, stringify } from 'query-string';
-import {
- IFieldType,
- IIndexPattern,
- IndexPatternField,
- KBN_FIELD_TYPES,
-} from '../../../../../../data/public';
-import { AppState } from '../../../angular/discover_state';
-import { DiscoverServices } from '../../../../build_services';
-import { VisualizationsStart, VisTypeAlias } from '../../../../../../visualizations/public';
-import { AGGS_TERMS_SIZE_SETTING } from '../../../../../common';
-
-export function isMapsAppRegistered(visualizations: VisualizationsStart) {
- return visualizations.getAliases().some(({ name }: VisTypeAlias) => {
- return name === 'maps';
- });
-}
-
-export function isFieldVisualizable(field: IFieldType, visualizations: VisualizationsStart) {
- if (field.name === '_id') {
- // Else you'd get a 'Fielddata access on the _id field is disallowed' error on ES side.
- return false;
- }
- if (
- (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
- isMapsAppRegistered(visualizations)
- ) {
- return true;
- }
- return field.visualizable;
-}
-
-export function getMapsAppUrl(
- field: IFieldType,
- indexPattern: IIndexPattern,
- appState: AppState,
- columns: string[]
-) {
- const mapAppParams = new URLSearchParams();
-
- // Copy global state
- const locationSplit = window.location.hash.split('?');
- if (locationSplit.length > 1) {
- const discoverParams = new URLSearchParams(locationSplit[1]);
- const globalStateUrlValue = discoverParams.get('_g');
- if (globalStateUrlValue) {
- mapAppParams.set('_g', globalStateUrlValue);
- }
- }
-
- // Copy filters and query in app state
- const mapsAppState: any = {
- filters: appState.filters || [],
- };
- if (appState.query) {
- mapsAppState.query = appState.query;
- }
- // @ts-ignore
- mapAppParams.set('_a', rison.encode(mapsAppState));
-
- // create initial layer descriptor
- const hasColumns = columns && columns.length && columns[0] !== '_source';
- const supportsClustering = field.aggregatable;
- mapAppParams.set(
- 'initialLayers',
- // @ts-ignore
- rison.encode_array([
- {
- id: uuid(),
- label: indexPattern.title,
- sourceDescriptor: {
- id: uuid(),
- type: 'ES_SEARCH',
- geoField: field.name,
- tooltipProperties: hasColumns ? columns : [],
- indexPatternId: indexPattern.id,
- scalingType: supportsClustering ? 'CLUSTERS' : 'LIMIT',
- },
- visible: true,
- type: supportsClustering ? 'BLENDED_VECTOR' : 'VECTOR',
- },
- ])
- );
-
- return {
- app: 'maps',
- path: `/map#?${mapAppParams.toString()}`,
- };
-}
-
-export function getVisualizeUrl(
- field: IndexPatternField,
- indexPattern: IIndexPattern,
- state: AppState,
- columns: string[],
- services: DiscoverServices
-) {
- const aggsTermSize = services.uiSettings.get(AGGS_TERMS_SIZE_SETTING);
- const urlParams = parse(services.history().location.search) as Record;
-
- if (
- (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
- isMapsAppRegistered(services.visualizations)
- ) {
- return getMapsAppUrl(field, indexPattern, state, columns);
- }
-
- let agg;
- const isGeoPoint = field.type === KBN_FIELD_TYPES.GEO_POINT;
- const type = isGeoPoint ? 'tile_map' : 'histogram';
- // If we're visualizing a date field, and our index is time based (and thus has a time filter),
- // then run a date histogram
- if (field.type === 'date' && indexPattern.timeFieldName === field.name) {
- agg = {
- type: 'date_histogram',
- schema: 'segment',
- params: {
- field: field.name,
- interval: 'auto',
- },
- };
- } else if (isGeoPoint) {
- agg = {
- type: 'geohash_grid',
- schema: 'segment',
- params: {
- field: field.name,
- precision: 3,
- },
- };
- } else {
- agg = {
- type: 'terms',
- schema: 'segment',
- params: {
- field: field.name,
- size: parseInt(aggsTermSize, 10),
- orderBy: '1',
- },
- };
- }
- const linkUrlParams = {
- ...urlParams,
- ...{
- indexPattern: state.index!,
- type,
- _a: rison.encode({
- filters: state.filters || [],
- query: state.query,
- vis: {
- type,
- aggs: [{ schema: 'metric', type: 'count', id: '1' }, agg],
- },
- } as any),
- },
- };
-
- return {
- app: 'visualize',
- path: `#/create?${stringify(linkUrlParams)}`,
- };
-}
diff --git a/src/plugins/discover/public/application/components/sidebar/types.ts b/src/plugins/discover/public/application/components/sidebar/types.ts
index e86138761c747..d80662b65cc7b 100644
--- a/src/plugins/discover/public/application/components/sidebar/types.ts
+++ b/src/plugins/discover/public/application/components/sidebar/types.ts
@@ -27,10 +27,7 @@ export interface FieldDetails {
exists: number;
total: boolean;
buckets: Bucket[];
- visualizeUrl: {
- app: string;
- path: string;
- };
+ columns: string[];
}
export interface Bucket {
diff --git a/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx
index bf417f9f1890b..fdb0ff973dcf0 100644
--- a/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx
+++ b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx
@@ -20,8 +20,6 @@ import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { ReactWrapper } from 'enzyme';
import { SkipBottomButton, SkipBottomButtonProps } from './skip_bottom_button';
-// @ts-ignore
-import { findTestSubject } from '@elastic/eui/lib/test';
describe('Skip to Bottom Button', function () {
let props: SkipBottomButtonProps;
diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx
index 29659b3969365..07e9e0a129a26 100644
--- a/src/plugins/discover/public/application/components/table/table.test.tsx
+++ b/src/plugins/discover/public/application/components/table/table.test.tsx
@@ -18,7 +18,6 @@
*/
import React from 'react';
import { mount } from 'enzyme';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { DocViewTable } from './table';
import { indexPatterns, IndexPattern } from '../../../../../data/public';
@@ -75,6 +74,8 @@ describe('DocViewTable at Discover', () => {
const hit = {
_index: 'logstash-2014.09.09',
+ _type: 'doc',
+ _id: 'id123',
_score: 1,
_source: {
message:
@@ -192,6 +193,8 @@ describe('DocViewTable at Discover Doc', () => {
const hit = {
_index: 'logstash-2014.09.09',
_score: 1,
+ _type: 'doc',
+ _id: 'id123',
_source: {
extension: 'html',
not_mapped: 'yes',
@@ -214,6 +217,9 @@ describe('DocViewTable at Discover Context', () => {
// here no toggleColumnButtons are rendered
const hit = {
_index: 'logstash-2014.09.09',
+ _type: 'doc',
+ _id: 'id123',
+ _score: 1,
_source: {
message:
'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. \
diff --git a/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx b/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx
index 964f94ca9d9b2..a4c10e749d868 100644
--- a/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx
+++ b/src/plugins/discover/public/application/components/timechart_header/timechart_header.test.tsx
@@ -21,7 +21,6 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { ReactWrapper } from 'enzyme';
import { TimechartHeader, TimechartHeaderProps } from './timechart_header';
import { EuiIconTip } from '@elastic/eui';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
describe('timechart header', function () {
diff --git a/src/plugins/discover/public/application/doc_views/doc_views_types.ts b/src/plugins/discover/public/application/doc_views/doc_views_types.ts
index 0c86c4f812749..6c90861e26727 100644
--- a/src/plugins/discover/public/application/doc_views/doc_views_types.ts
+++ b/src/plugins/discover/public/application/doc_views/doc_views_types.ts
@@ -18,6 +18,7 @@
*/
import { ComponentType } from 'react';
import { IScope } from 'angular';
+import { SearchResponse } from 'elasticsearch';
import { IndexPattern } from '../../../../data/public';
export interface AngularDirective {
@@ -27,7 +28,7 @@ export interface AngularDirective {
export type AngularScope = IScope;
-export type ElasticSearchHit = Record>;
+export type ElasticSearchHit = SearchResponse['hits']['hits'][number];
export interface FieldMapping {
filterable?: boolean;
diff --git a/src/plugins/discover/public/get_inner_angular.ts b/src/plugins/discover/public/get_inner_angular.ts
index 0b3c2fad8d45b..85b0752f13463 100644
--- a/src/plugins/discover/public/get_inner_angular.ts
+++ b/src/plugins/discover/public/get_inner_angular.ts
@@ -110,7 +110,6 @@ export function initializeInnerAngularModule(
createLocalPromiseModule();
createLocalTopNavModule(navigation);
createLocalStorageModule();
- createElasticSearchModule(data);
createPagerFactoryModule();
createDocTableModule();
initialized = true;
@@ -145,7 +144,6 @@ export function initializeInnerAngularModule(
'discoverPromise',
'discoverTopNav',
'discoverLocalStorageProvider',
- 'discoverEs',
'discoverDocTable',
'discoverPagerFactory',
])
@@ -201,16 +199,6 @@ const createLocalStorageService = function (type: string) {
};
};
-function createElasticSearchModule(data: DataPublicPluginStart) {
- angular
- .module('discoverEs', [])
- // Elasticsearch client used for requesting data. Connects to the /elasticsearch proxy
- // have to be written as function expression, because it's not compiled in dev mode
- .service('es', function () {
- return data.search.__LEGACY.esClient;
- });
-}
-
function createPagerFactoryModule() {
angular.module('discoverPagerFactory', []).factory('pagerFactory', createPagerFactory);
}
diff --git a/src/plugins/discover/public/kibana_services.ts b/src/plugins/discover/public/kibana_services.ts
index ecb5d7fd90283..bc25fa71dcf41 100644
--- a/src/plugins/discover/public/kibana_services.ts
+++ b/src/plugins/discover/public/kibana_services.ts
@@ -20,6 +20,7 @@
import _ from 'lodash';
import { createHashHistory } from 'history';
import { ScopedHistory } from 'kibana/public';
+import { UiActionsStart } from 'src/plugins/ui_actions/public';
import { DiscoverServices } from './build_services';
import { createGetterSetter } from '../../kibana_utils/public';
import { search } from '../../data/public';
@@ -27,6 +28,7 @@ import { DocViewsRegistry } from './application/doc_views/doc_views_registry';
let angularModule: any = null;
let services: DiscoverServices | null = null;
+let uiActions: UiActionsStart;
/**
* set bootstrapped inner angular module
@@ -53,6 +55,9 @@ export function setServices(newServices: any) {
services = newServices;
}
+export const setUiActions = (pluginUiActions: UiActionsStart) => (uiActions = pluginUiActions);
+export const getUiActions = () => uiActions;
+
export const [getUrlTracker, setUrlTracker] = createGetterSetter<{
setTrackedUrl: (url: string) => void;
restorePreviousUrl: () => void;
diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts
index 20e13d204e0e9..015f4267646c1 100644
--- a/src/plugins/discover/public/plugin.ts
+++ b/src/plugins/discover/public/plugin.ts
@@ -53,6 +53,7 @@ import {
setUrlTracker,
setAngularModule,
setServices,
+ setUiActions,
setScopedHistory,
getScopedHistory,
syncHistoryLocations,
@@ -314,6 +315,8 @@ export class DiscoverPlugin
this.innerAngularInitialized = true;
};
+ setUiActions(plugins.uiActions);
+
this.initializeServices = async () => {
if (this.servicesInitialized) {
return { core, plugins };
diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts
index 636ce3e623c5b..88c1a5917e609 100644
--- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts
+++ b/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts
@@ -19,6 +19,7 @@
import { createFilterAction } from './apply_filter_action';
import { expectErrorAsync } from '../../tests/helpers';
+import { defaultTrigger } from '../../../../ui_actions/public/triggers';
test('has ACTION_APPLY_FILTER type and id', () => {
const action = createFilterAction();
@@ -51,6 +52,7 @@ describe('isCompatible()', () => {
}),
} as any,
filters: [],
+ trigger: defaultTrigger,
});
expect(result).toBe(true);
});
@@ -66,6 +68,7 @@ describe('isCompatible()', () => {
}),
} as any,
filters: [],
+ trigger: defaultTrigger,
});
expect(result).toBe(false);
});
@@ -119,6 +122,7 @@ describe('execute()', () => {
await action.execute({
embeddable,
filters: ['FILTER' as any],
+ trigger: defaultTrigger,
});
expect(root.updateInput).toHaveBeenCalledTimes(1);
diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx
index 594a7ad73c396..8c3d7ab9c30d0 100644
--- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx
+++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx
@@ -56,12 +56,18 @@ test('is compatible when edit url is available, in edit mode and editable', asyn
test('redirects to app using state transfer', async () => {
applicationMock.currentAppId$ = of('superCoolCurrentApp');
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
- const embeddable = new EditableEmbeddable({ id: '123', viewMode: ViewMode.EDIT }, true);
+ const input = { id: '123', viewMode: ViewMode.EDIT };
+ const embeddable = new EditableEmbeddable(input, true);
embeddable.getOutput = jest.fn(() => ({ editApp: 'ultraVisualize', editPath: '/123' }));
await action.execute({ embeddable });
expect(stateTransferMock.navigateToEditor).toHaveBeenCalledWith('ultraVisualize', {
path: '/123',
- state: { originatingApp: 'superCoolCurrentApp' },
+ state: {
+ originatingApp: 'superCoolCurrentApp',
+ byValueMode: true,
+ embeddableId: '123',
+ valueInput: input,
+ },
});
});
diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts
index 9177a77d547b0..8d12ddd0299e7 100644
--- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts
+++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts
@@ -24,7 +24,12 @@ import { take } from 'rxjs/operators';
import { ViewMode } from '../types';
import { EmbeddableFactoryNotFoundError } from '../errors';
import { EmbeddableStart } from '../../plugin';
-import { IEmbeddable, EmbeddableEditorState, EmbeddableStateTransfer } from '../..';
+import {
+ IEmbeddable,
+ EmbeddableEditorState,
+ EmbeddableStateTransfer,
+ SavedObjectEmbeddableInput,
+} from '../..';
export const ACTION_EDIT_PANEL = 'editPanel';
@@ -109,8 +114,17 @@ export class EditPanelAction implements Action {
const app = embeddable ? embeddable.getOutput().editApp : undefined;
const path = embeddable ? embeddable.getOutput().editPath : undefined;
if (app && path) {
- const state = this.currentAppId ? { originatingApp: this.currentAppId } : undefined;
- return { app, path, state };
+ if (this.currentAppId) {
+ const byValueMode = !(embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId;
+ const state: EmbeddableEditorState = {
+ originatingApp: this.currentAppId,
+ byValueMode,
+ valueInput: byValueMode ? embeddable.getInput() : undefined,
+ embeddableId: embeddable.id,
+ };
+ return { app, path, state };
+ }
+ return { app, path };
}
}
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx
index 131069909dd2a..cb900884fde97 100644
--- a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx
@@ -20,7 +20,6 @@ import React from 'react';
import { HelloWorldEmbeddable } from '../../../../../../examples/embeddable_examples/public';
import { EmbeddableRoot } from './embeddable_root';
import { mount } from 'enzyme';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
test('EmbeddableRoot renders an embeddable', async () => {
diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
index 341a51d7348b2..fcf79c1d6b211 100644
--- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
@@ -21,7 +21,6 @@ import React from 'react';
import { mount } from 'enzyme';
import { nextTick } from 'test_utils/enzyme_helpers';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { I18nProvider } from '@kbn/i18n/react';
import { CONTEXT_MENU_TRIGGER } from '../triggers';
diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
index cb02ffc470e95..d8659680dceb9 100644
--- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
+++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
@@ -30,6 +30,7 @@ import {
PANEL_BADGE_TRIGGER,
PANEL_NOTIFICATION_TRIGGER,
EmbeddableContext,
+ contextMenuTrigger,
} from '../triggers';
import { IEmbeddable, EmbeddableOutput, EmbeddableError } from '../embeddables/i_embeddable';
import { ViewMode } from '../types';
@@ -311,7 +312,11 @@ export class EmbeddablePanel extends React.Component {
const sortedActions = [...regularActions, ...extraActions].sort(sortByOrderField);
return await buildContextMenuForActions({
- actions: sortedActions.map((action) => [action, { embeddable: this.props.embeddable }]),
+ actions: sortedActions.map((action) => ({
+ action,
+ context: { embeddable: this.props.embeddable },
+ trigger: contextMenuTrigger,
+ })),
closeMenu: this.closeMyContextMenuPanel,
});
};
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx
index d8def3147e52c..0361939fd07e6 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx
@@ -31,6 +31,7 @@ import { ContactCardEmbeddable } from '../../../../test_samples';
import { esFilters, Filter } from '../../../../../../../../plugins/data/public';
import { EmbeddableStart } from '../../../../../plugin';
import { embeddablePluginMock } from '../../../../../mocks';
+import { defaultTrigger } from '../../../../../../../ui_actions/public/triggers';
const { setup, doStart } = embeddablePluginMock.createInstance();
setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
@@ -85,7 +86,9 @@ test('Is not compatible when container is in view mode', async () => {
() => null
);
container.updateInput({ viewMode: ViewMode.VIEW });
- expect(await addPanelAction.isCompatible({ embeddable: container })).toBe(false);
+ expect(
+ await addPanelAction.isCompatible({ embeddable: container, trigger: defaultTrigger })
+ ).toBe(false);
});
test('Is not compatible when embeddable is not a container', async () => {
@@ -94,7 +97,7 @@ test('Is not compatible when embeddable is not a container', async () => {
test('Is compatible when embeddable is a parent and in edit mode', async () => {
container.updateInput({ viewMode: ViewMode.EDIT });
- expect(await action.isCompatible({ embeddable: container })).toBe(true);
+ expect(await action.isCompatible({ embeddable: container, trigger: defaultTrigger })).toBe(true);
});
test('Execute throws an error when called with an embeddable that is not a container', async () => {
@@ -108,6 +111,7 @@ test('Execute throws an error when called with an embeddable that is not a conta
},
{} as any
),
+ trigger: defaultTrigger,
} as any);
}
await expect(check()).rejects.toThrow(Error);
@@ -116,6 +120,7 @@ test('Execute does not throw an error when called with a compatible container',
container.updateInput({ viewMode: ViewMode.EDIT });
await action.execute({
embeddable: container,
+ trigger: defaultTrigger,
});
});
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts
index f3a483bb4bda4..63575273bbf62 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts
@@ -17,7 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
-import { Action } from 'src/plugins/ui_actions/public';
+import { Action, ActionExecutionContext } from 'src/plugins/ui_actions/public';
import { NotificationsStart, OverlayStart } from 'src/core/public';
import { EmbeddableStart } from 'src/plugins/embeddable/public/plugin';
import { ViewMode } from '../../../../types';
@@ -52,12 +52,14 @@ export class AddPanelAction implements Action {
return 'plusInCircleFilled';
}
- public async isCompatible({ embeddable }: ActionContext) {
+ public async isCompatible(context: ActionExecutionContext) {
+ const { embeddable } = context;
return embeddable.getIsContainer() && embeddable.getInput().viewMode === ViewMode.EDIT;
}
- public async execute({ embeddable }: ActionContext) {
- if (!embeddable.getIsContainer() || !(await this.isCompatible({ embeddable }))) {
+ public async execute(context: ActionExecutionContext) {
+ const { embeddable } = context;
+ if (!embeddable.getIsContainer() || !(await this.isCompatible(context))) {
throw new Error('Context is incompatible');
}
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx
index 34a176400dbb9..95aee3d9cb335 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx
@@ -30,7 +30,6 @@ import { ContainerInput } from '../../../../containers';
import { mountWithIntl as mount } from 'test_utils/enzyme_helpers';
import { ReactWrapper } from 'enzyme';
import { coreMock } from '../../../../../../../../core/public/mocks';
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { embeddablePluginMock } from '../../../../../mocks';
@@ -125,7 +124,7 @@ test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()'
notifications={core.notifications}
SavedObjectFinder={(props) => }
/>
- ) as ReactWrapper;
+ ) as ReactWrapper;
const spy = jest.fn();
component.instance().createNewEmbeddable = spy;
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts
index 6fddcbc84faf7..dbfa55a4e0f13 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts
@@ -18,8 +18,6 @@
*/
import { Container, isErrorEmbeddable } from '../../../..';
-// @ts-ignore
-import { findTestSubject } from '@elastic/eui/lib/test';
import { nextTick } from 'test_utils/enzyme_helpers';
import { CustomizePanelTitleAction } from './customize_panel_action';
import {
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx
index 2f086a3fb2c0c..5d7daaa7217ed 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx
@@ -30,7 +30,7 @@ import React from 'react';
import { Action } from 'src/plugins/ui_actions/public';
import { PanelOptionsMenu } from './panel_options_menu';
import { IEmbeddable } from '../../embeddables';
-import { EmbeddableContext } from '../../triggers';
+import { EmbeddableContext, panelBadgeTrigger, panelNotificationTrigger } from '../../triggers';
export interface PanelHeaderProps {
title?: string;
@@ -49,11 +49,11 @@ function renderBadges(badges: Array>, embeddable: IEmb
badge.execute({ embeddable })}
- onClickAriaLabel={badge.getDisplayName({ embeddable })}
+ iconType={badge.getIconType({ embeddable, trigger: panelBadgeTrigger })}
+ onClick={() => badge.execute({ embeddable, trigger: panelBadgeTrigger })}
+ onClickAriaLabel={badge.getDisplayName({ embeddable, trigger: panelBadgeTrigger })}
>
- {badge.getDisplayName({ embeddable })}
+ {badge.getDisplayName({ embeddable, trigger: panelBadgeTrigger })}
));
}
@@ -70,14 +70,17 @@ function renderNotifications(
data-test-subj={`embeddablePanelNotification-${notification.id}`}
key={notification.id}
style={{ marginTop: '4px', marginRight: '4px' }}
- onClick={() => notification.execute(context)}
+ onClick={() => notification.execute({ ...context, trigger: panelNotificationTrigger })}
>
- {notification.getDisplayName(context)}
+ {notification.getDisplayName({ ...context, trigger: panelNotificationTrigger })}
);
if (notification.getDisplayNameTooltip) {
- const tooltip = notification.getDisplayNameTooltip(context);
+ const tooltip = notification.getDisplayNameTooltip({
+ ...context,
+ trigger: panelNotificationTrigger,
+ });
if (tooltip) {
badge = (
diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts
index a6721784302ac..3f3456d914728 100644
--- a/src/plugins/embeddable/public/lib/state_transfer/types.ts
+++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts
@@ -27,6 +27,7 @@ export interface EmbeddableEditorState {
originatingApp: string;
byValueMode?: boolean;
valueInput?: EmbeddableInput;
+ embeddableId?: string;
}
export function isEmbeddableEditorState(state: unknown): state is EmbeddableEditorState {
@@ -49,6 +50,7 @@ export interface EmbeddablePackageByReferenceState {
export interface EmbeddablePackageByValueState {
type: string;
input: EmbeddableInput;
+ embeddableId?: string;
}
export type EmbeddablePackageState =
diff --git a/src/plugins/embeddable/public/lib/test_samples/actions/say_hello_action.tsx b/src/plugins/embeddable/public/lib/test_samples/actions/say_hello_action.tsx
index 0612b838a6ce7..968caf67b1826 100644
--- a/src/plugins/embeddable/public/lib/test_samples/actions/say_hello_action.tsx
+++ b/src/plugins/embeddable/public/lib/test_samples/actions/say_hello_action.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import { ActionByType, IncompatibleActionError, ActionType } from '../../ui_actions';
+import { IncompatibleActionError, ActionType, ActionDefinitionByType } from '../../ui_actions';
import { EmbeddableInput, Embeddable, EmbeddableOutput, IEmbeddable } from '../../embeddables';
// Casting to ActionType is a hack - in a real situation use
@@ -42,7 +42,7 @@ export interface SayHelloActionContext {
message?: string;
}
-export class SayHelloAction implements ActionByType {
+export class SayHelloAction implements ActionDefinitionByType {
public readonly type = SAY_HELLO_ACTION;
public readonly id = SAY_HELLO_ACTION;
diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx
index fa79af909a427..7ec03ba659cda 100644
--- a/src/plugins/embeddable/public/mocks.tsx
+++ b/src/plugins/embeddable/public/mocks.tsx
@@ -35,6 +35,7 @@ import { dataPluginMock } from '../../data/public/mocks';
import { inspectorPluginMock } from '../../inspector/public/mocks';
import { uiActionsPluginMock } from '../../ui_actions/public/mocks';
+import { SavedObjectEmbeddableInput, ReferenceOrValueEmbeddable, EmbeddableInput } from './lib';
export type Setup = jest.Mocked;
export type Start = jest.Mocked;
@@ -84,6 +85,25 @@ export const createEmbeddableStateTransferMock = (): Partial(
+ embeddable: IEmbeddable,
+ options: {
+ mockedByReferenceInput: RefTypeInput;
+ mockedByValueInput: ValTypeInput;
+ }
+): OriginalEmbeddableType & ReferenceOrValueEmbeddable => {
+ const newEmbeddable: ReferenceOrValueEmbeddable = (embeddable as unknown) as ReferenceOrValueEmbeddable;
+ newEmbeddable.inputIsRefType = (input: unknown): input is RefTypeInput =>
+ !!(input as RefTypeInput).savedObjectId;
+ newEmbeddable.getInputAsRefType = () => Promise.resolve(options.mockedByReferenceInput);
+ newEmbeddable.getInputAsValueType = () => Promise.resolve(options.mockedByValueInput);
+ return newEmbeddable as OriginalEmbeddableType & ReferenceOrValueEmbeddable;
+};
+
const createSetupContract = (): Setup => {
const setupContract: Setup = {
registerEmbeddableFactory: jest.fn(),
@@ -126,4 +146,5 @@ export const embeddablePluginMock = {
createSetupContract,
createStartContract,
createInstance,
+ mockRefOrValEmbeddable,
};
diff --git a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
index 9d765c9906443..f8c4a4a7e4b72 100644
--- a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
+++ b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
@@ -31,6 +31,7 @@ import {
FilterableEmbeddableInput,
} from '../lib/test_samples';
import { esFilters } from '../../../data/public';
+import { applyFilterTrigger } from '../../../ui_actions/public';
test('ApplyFilterAction applies the filter to the root of the container tree', async () => {
const { doStart, setup } = testPlugin();
@@ -85,7 +86,7 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a
query: { match: { extension: { query: 'foo' } } },
};
- await applyFilterAction.execute({ embeddable, filters: [filter] });
+ await applyFilterAction.execute({ embeddable, filters: [filter], trigger: applyFilterTrigger });
expect(root.getInput().filters.length).toBe(1);
expect(node1.getInput().filters.length).toBe(1);
expect(embeddable.getInput().filters.length).toBe(1);
diff --git a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
index e094afe528498..d12a55dd827e6 100644
--- a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
+++ b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
@@ -17,7 +17,6 @@
* under the License.
*/
-// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import * as React from 'react';
import { Container, isErrorEmbeddable } from '../lib';
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
index f00beb470a9fc..eead90d2f75b7 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
@@ -17,9 +17,10 @@
* under the License.
*/
import React, { useEffect } from 'react';
+import { act } from 'react-dom/test-utils';
-import { registerTestBed } from '../shared_imports';
-import { OnUpdateHandler } from '../types';
+import { registerTestBed, TestBed } from '../shared_imports';
+import { FormHook, OnUpdateHandler, FieldConfig } from '../types';
import { useForm } from '../hooks/use_form';
import { Form } from './form';
import { UseField } from './use_field';
@@ -62,4 +63,91 @@ describe(' ', () => {
lastName: 'Snow',
});
});
+
+ describe('serializer(), deserializer(), formatter()', () => {
+ interface MyForm {
+ name: string;
+ }
+
+ const serializer = jest.fn();
+ const deserializer = jest.fn();
+ const formatter = jest.fn();
+
+ const fieldConfig: FieldConfig = {
+ defaultValue: '',
+ serializer,
+ deserializer,
+ formatters: [formatter],
+ };
+
+ let formHook: FormHook | null = null;
+
+ beforeEach(() => {
+ formHook = null;
+ serializer.mockReset().mockImplementation((value) => `${value}-serialized`);
+ deserializer.mockReset().mockImplementation((value) => `${value}-deserialized`);
+ formatter.mockReset().mockImplementation((value: string) => value.toUpperCase());
+ });
+
+ const onFormHook = (_form: FormHook) => {
+ formHook = _form;
+ };
+
+ const TestComp = ({ onForm }: { onForm: (form: FormHook) => void }) => {
+ const { form } = useForm({ defaultValue: { name: 'John' } });
+
+ useEffect(() => {
+ onForm(form);
+ }, [form]);
+
+ return (
+
+ );
+ };
+
+ test('should call each handler at expected lifecycle', async () => {
+ const setup = registerTestBed(TestComp, {
+ memoryRouter: { wrapComponent: false },
+ defaultProps: { onForm: onFormHook },
+ });
+
+ const testBed = setup() as TestBed;
+
+ if (!formHook) {
+ throw new Error(
+ `formHook is not defined. Use the onForm() prop to update the reference to the form hook.`
+ );
+ }
+
+ const { form } = testBed;
+
+ expect(deserializer).toBeCalled();
+ expect(serializer).not.toBeCalled();
+ expect(formatter).not.toBeCalled();
+
+ let formData = formHook.getFormData({ unflatten: false });
+ expect(formData.name).toEqual('John-deserialized');
+
+ await act(async () => {
+ form.setInputValue('myField', 'Mike');
+ });
+
+ expect(formatter).toBeCalled(); // Formatters are executed on each value change
+ expect(serializer).not.toBeCalled(); // Serializer are executed *only** when outputting the form data
+
+ formData = formHook.getFormData();
+ expect(serializer).toBeCalled();
+ expect(formData.name).toEqual('MIKE-serialized');
+
+ // Make sure that when we reset the form values, we don't serialize the fields
+ serializer.mockReset();
+
+ await act(async () => {
+ formHook!.reset();
+ });
+ expect(serializer).not.toBeCalled();
+ });
+ });
});
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts
index 15ea99eb6cc3a..caf75b42598f5 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts
@@ -118,15 +118,13 @@ export const useField = (
setIsChangingValue(true);
}
- const newValue = serializeOutput(value);
-
// Notify listener
if (valueChangeListener) {
- valueChangeListener(newValue as T);
+ valueChangeListener(value);
}
// Update the form data observable
- __updateFormDataAt(path, newValue);
+ __updateFormDataAt(path, value);
// Validate field(s) and update form.isValid state
await __validateFields(fieldsToValidateOnChange ?? [path]);
@@ -153,7 +151,6 @@ export const useField = (
}
}
}, [
- serializeOutput,
valueChangeListener,
errorDisplayDelay,
path,
@@ -442,13 +439,7 @@ export const useField = (
if (resetValue) {
setValue(initialValue);
- /**
- * Having to call serializeOutput() is a current bug of the lib and will be fixed
- * in a future PR. The serializer function should only be called when outputting
- * the form data. If we need to continuously format the data while it changes,
- * we need to use the field `formatter` config.
- */
- return serializeOutput(initialValue);
+ return initialValue;
}
},
[setValue, serializeOutput, initialValue]
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx
index 216c7974a9679..b2cc91152b571 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx
@@ -22,7 +22,7 @@ import { act } from 'react-dom/test-utils';
import { registerTestBed, getRandomString, TestBed } from '../shared_imports';
import { Form, UseField } from '../components';
-import { FormSubmitHandler, OnUpdateHandler } from '../types';
+import { FormSubmitHandler, OnUpdateHandler, FormHook, ValidationFunc } from '../types';
import { useForm } from './use_form';
interface MyForm {
@@ -123,6 +123,71 @@ describe('use_form() hook', () => {
expect(formData).toEqual(expectedData);
});
+
+ test('should not build the object if the form is not valid', async () => {
+ let formHook: FormHook | null = null;
+
+ const onFormHook = (_form: FormHook) => {
+ formHook = _form;
+ };
+
+ const TestComp = ({ onForm }: { onForm: (form: FormHook) => void }) => {
+ const { form } = useForm({ defaultValue: { username: 'initialValue' } });
+ const validator: ValidationFunc = ({ value }) => {
+ if (value === 'wrongValue') {
+ return { message: 'Error on the field' };
+ }
+ };
+
+ useEffect(() => {
+ onForm(form);
+ }, [form]);
+
+ return (
+
+ );
+ };
+
+ const setup = registerTestBed(TestComp, {
+ defaultProps: { onForm: onFormHook },
+ memoryRouter: { wrapComponent: false },
+ });
+
+ const {
+ form: { setInputValue },
+ } = setup() as TestBed;
+
+ if (!formHook) {
+ throw new Error(
+ `formHook is not defined. Use the onForm() prop to update the reference to the form hook.`
+ );
+ }
+
+ let data;
+ let isValid;
+
+ await act(async () => {
+ ({ data, isValid } = await formHook!.submit());
+ });
+
+ expect(isValid).toBe(true);
+ expect(data).toEqual({ username: 'initialValue' });
+
+ setInputValue('myField', 'wrongValue'); // Validation will fail
+
+ await act(async () => {
+ ({ data, isValid } = await formHook!.submit());
+ });
+
+ expect(isValid).toBe(false);
+ expect(data).toEqual({}); // Don't build the object (and call the serializers()) when invalid
+ });
});
describe('form.subscribe()', () => {
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts
index 46b8958491e56..1f51b75a80b21 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts
@@ -140,7 +140,7 @@ export function useForm(
return Object.entries(fieldsRefs.current).reduce(
(acc, [key, field]) => ({
...acc,
- [key]: field.__serializeOutput(),
+ [key]: field.value,
}),
{} as T
);
@@ -233,8 +233,7 @@ export function useForm(
fieldsRefs.current[field.path] = field;
if (!{}.hasOwnProperty.call(getFormData$().value, field.path)) {
- const fieldValue = field.__serializeOutput();
- updateFormDataAt(field.path, fieldValue);
+ updateFormDataAt(field.path, field.value);
}
},
[getFormData$, updateFormDataAt]
@@ -301,7 +300,7 @@ export function useForm(
setSubmitting(true);
const isFormValid = await validateAllFields();
- const formData = getFormData();
+ const formData = isFormValid ? getFormData() : ({} as T);
if (onSubmit) {
await onSubmit(formData, isFormValid!);
diff --git a/src/plugins/expressions/common/expression_types/specs/boolean.ts b/src/plugins/expressions/common/expression_types/specs/boolean.ts
index d730f95d7c423..adbdeafc34fd2 100644
--- a/src/plugins/expressions/common/expression_types/specs/boolean.ts
+++ b/src/plugins/expressions/common/expression_types/specs/boolean.ts
@@ -41,7 +41,8 @@ export const boolean: ExpressionTypeDefinition<'boolean', boolean> = {
},
datatable: (value): Datatable => ({
type: 'datatable',
- columns: [{ name: 'value', type: name }],
+ meta: {},
+ columns: [{ id: 'value', name: 'value', meta: { type: name } }],
rows: [{ value }],
}),
},
diff --git a/src/plugins/expressions/common/expression_types/specs/datatable.ts b/src/plugins/expressions/common/expression_types/specs/datatable.ts
index 5cd53df663e1d..dd3c653878de7 100644
--- a/src/plugins/expressions/common/expression_types/specs/datatable.ts
+++ b/src/plugins/expressions/common/expression_types/specs/datatable.ts
@@ -23,6 +23,13 @@ import { ExpressionTypeDefinition } from '../types';
import { PointSeries, PointSeriesColumn } from './pointseries';
import { ExpressionValueRender } from './render';
+type State = string | number | boolean | null | undefined | SerializableState;
+
+/** @internal **/
+export interface SerializableState {
+ [key: string]: State | State[];
+}
+
const name = 'datatable';
/**
@@ -42,12 +49,23 @@ export type DatatableColumnType = 'string' | 'number' | 'boolean' | 'date' | 'nu
*/
export type DatatableRow = Record;
+export interface DatatableColumnMeta {
+ type: DatatableColumnType;
+ field?: string;
+ params?: SerializableState;
+}
/**
* This type represents the shape of a column in a `Datatable`.
*/
export interface DatatableColumn {
+ id: string;
name: string;
- type: DatatableColumnType;
+ meta: DatatableColumnMeta;
+}
+
+export interface DatatableMeta {
+ type?: string;
+ source?: string;
}
/**
@@ -55,6 +73,7 @@ export interface DatatableColumn {
*/
export interface Datatable {
type: typeof name;
+ meta?: DatatableMeta;
columns: DatatableColumn[];
rows: DatatableRow[];
}
@@ -103,14 +122,16 @@ export const datatable: ExpressionTypeDefinition ({
type: name,
+ meta: {},
rows: [],
columns: [],
}),
pointseries: (value: PointSeries) => ({
type: name,
+ meta: {},
rows: value.rows,
columns: map(value.columns, (val: PointSeriesColumn, colName) => {
- return { name: colName, type: val.type };
+ return { id: colName, name: colName, meta: { type: val.type } };
}),
}),
},
@@ -127,13 +148,13 @@ export const datatable: ExpressionTypeDefinition {
const validFields = ['x', 'y', 'color', 'size', 'text'];
- const columns = table.columns.filter((column) => validFields.includes(column.name));
+ const columns = table.columns.filter((column) => validFields.includes(column.id));
const rows = table.rows.map((row) => pick(row, validFields));
return {
type: 'pointseries',
columns: columns.reduce>((acc, column) => {
acc[column.name] = {
- type: column.type,
+ type: column.meta.type,
expression: column.name,
role: 'dimension',
};
diff --git a/src/plugins/expressions/common/expression_types/specs/num.ts b/src/plugins/expressions/common/expression_types/specs/num.ts
index 191e617fdc858..041747f39740b 100644
--- a/src/plugins/expressions/common/expression_types/specs/num.ts
+++ b/src/plugins/expressions/common/expression_types/specs/num.ts
@@ -73,7 +73,8 @@ export const num: ExpressionTypeDefinition<'num', ExpressionValueNum> = {
},
datatable: ({ value }): Datatable => ({
type: 'datatable',
- columns: [{ name: 'value', type: 'number' }],
+ meta: {},
+ columns: [{ id: 'value', name: 'value', meta: { type: 'number' } }],
rows: [{ value }],
}),
},
diff --git a/src/plugins/expressions/common/expression_types/specs/number.ts b/src/plugins/expressions/common/expression_types/specs/number.ts
index 10986659c7848..c5fdacf3408a1 100644
--- a/src/plugins/expressions/common/expression_types/specs/number.ts
+++ b/src/plugins/expressions/common/expression_types/specs/number.ts
@@ -55,7 +55,8 @@ export const number: ExpressionTypeDefinition = {
},
datatable: (value): Datatable => ({
type: 'datatable',
- columns: [{ name: 'value', type: 'number' }],
+ meta: {},
+ columns: [{ id: 'value', name: 'value', meta: { type: 'number' } }],
rows: [{ value }],
}),
},
diff --git a/src/plugins/expressions/common/expression_types/specs/string.ts b/src/plugins/expressions/common/expression_types/specs/string.ts
index 46f460891c2fb..3d52707279bfc 100644
--- a/src/plugins/expressions/common/expression_types/specs/string.ts
+++ b/src/plugins/expressions/common/expression_types/specs/string.ts
@@ -40,7 +40,8 @@ export const string: ExpressionTypeDefinition = {
},
datatable: (value): Datatable => ({
type: 'datatable',
- columns: [{ name: 'value', type: 'string' }],
+ meta: {},
+ columns: [{ id: 'value', name: 'value', meta: { type: 'string' } }],
rows: [{ value }],
}),
},
diff --git a/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap b/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap
index 1e7b3d5c6284c..9a9e055d54d2f 100644
--- a/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap
+++ b/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap
@@ -245,22 +245,41 @@ exports[`bulkCreate should display error message when bulkCreate request fails 1
isLoading={false}
onClick={[Function]}
>
-
-
-
- Load Kibana objects
-
-
-
+
+
+ Load Kibana objects
+
+
+
+
+
@@ -565,22 +584,41 @@ exports[`bulkCreate should display success message when bulkCreate is successful
isLoading={false}
onClick={[Function]}
>
-
-
-
- Load Kibana objects
-
-
-
+
+
+ Load Kibana objects
+
+
+
+
+
diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap
index 6261ea2c90793..5218ebd1b4ad4 100644
--- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap
+++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap
@@ -33,7 +33,7 @@ exports[`Header should render normally 1`] = `
type="button"
>
-
-
-
-
-
+
+
+
- View: View 1
-
+
+ View: View 1
+
+
-
+
diff --git a/src/plugins/kibana_react/public/field_button/__snapshots__/field_button.test.tsx.snap b/src/plugins/kibana_react/public/field_button/__snapshots__/field_button.test.tsx.snap
new file mode 100644
index 0000000000000..e65b5fcb8fbbd
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/__snapshots__/field_button.test.tsx.snap
@@ -0,0 +1,134 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`fieldAction is rendered 1`] = `
+
+
+
+ name
+
+
+
+
+ fieldAction
+
+
+
+`;
+
+exports[`fieldIcon is rendered 1`] = `
+
+
+
+
+ fieldIcon
+
+
+
+ name
+
+
+
+`;
+
+exports[`isActive defaults to false 1`] = `
+
+
+
+ name
+
+
+
+`;
+
+exports[`isActive renders true 1`] = `
+
+
+
+ name
+
+
+
+`;
+
+exports[`isDraggable is rendered 1`] = `
+
+
+
+ name
+
+
+
+`;
+
+exports[`sizes m is applied 1`] = `
+
+
+
+ name
+
+
+
+`;
+
+exports[`sizes s is applied 1`] = `
+
+
+
+ name
+
+
+
+`;
diff --git a/src/plugins/kibana_react/public/field_button/field_button.scss b/src/plugins/kibana_react/public/field_button/field_button.scss
new file mode 100644
index 0000000000000..43f60e4503576
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/field_button.scss
@@ -0,0 +1,75 @@
+.kbnFieldButton {
+ @include euiFontSizeS;
+ border-radius: $euiBorderRadius;
+ margin-bottom: $euiSizeXS;
+ display: flex;
+ align-items: center;
+ transition: box-shadow $euiAnimSpeedFast $euiAnimSlightResistance,
+ background-color $euiAnimSpeedFast $euiAnimSlightResistance; // sass-lint:disable-line indentation
+
+ &:focus-within,
+ &-isActive {
+ @include euiFocusRing;
+ }
+}
+
+.kbnFieldButton--isDraggable {
+ background: lightOrDarkTheme($euiColorEmptyShade, $euiColorLightestShade);
+
+ &:hover,
+ &:focus,
+ &:focus-within {
+ @include euiBottomShadowMedium;
+ border-radius: $euiBorderRadius;
+ z-index: 2;
+ }
+
+ .kbnFieldButton__button {
+ &:hover,
+ &:focus {
+ cursor: grab;
+ }
+ }
+}
+
+.kbnFieldButton__button {
+ flex-grow: 1;
+ text-align: left;
+ padding: $euiSizeS;
+ display: flex;
+ align-items: flex-start;
+}
+
+.kbnFieldButton__fieldIcon {
+ flex-shrink: 0;
+ line-height: 0;
+ margin-right: $euiSizeS;
+}
+
+.kbnFieldButton__name {
+ flex-grow: 1;
+ word-break: break-word;
+}
+
+.kbnFieldButton__infoIcon {
+ flex-shrink: 0;
+ margin-left: $euiSizeXS;
+}
+
+.kbnFieldButton__fieldAction {
+ margin-right: $euiSizeS;
+}
+
+// Reduce text size and spacing for the small size
+.kbnFieldButton--small {
+ font-size: $euiFontSizeXS;
+
+ .kbnFieldButton__button {
+ padding: $euiSizeXS;
+ }
+
+ .kbnFieldButton__fieldIcon,
+ .kbnFieldButton__fieldAction {
+ margin-right: $euiSizeXS;
+ }
+}
diff --git a/src/plugins/kibana_react/public/field_button/field_button.test.tsx b/src/plugins/kibana_react/public/field_button/field_button.test.tsx
new file mode 100644
index 0000000000000..32e1203b89718
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/field_button.test.tsx
@@ -0,0 +1,68 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { shallow } from 'enzyme';
+import { FieldButton, SIZES } from './field_button';
+
+const noop = () => {};
+
+describe('sizes', () => {
+ SIZES.forEach((size) => {
+ test(`${size} is applied`, () => {
+ const component = shallow(
);
+ expect(component).toMatchSnapshot();
+ });
+ });
+});
+
+describe('isDraggable', () => {
+ it('is rendered', () => {
+ const component = shallow(
);
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('fieldIcon', () => {
+ it('is rendered', () => {
+ const component = shallow(
+
fieldIcon} />
+ );
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('fieldAction', () => {
+ it('is rendered', () => {
+ const component = shallow(
+ fieldAction} />
+ );
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('isActive', () => {
+ it('defaults to false', () => {
+ const component = shallow( );
+ expect(component).toMatchSnapshot();
+ });
+ it('renders true', () => {
+ const component = shallow( );
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/src/plugins/kibana_react/public/field_button/field_button.tsx b/src/plugins/kibana_react/public/field_button/field_button.tsx
new file mode 100644
index 0000000000000..e5833b261946a
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/field_button.tsx
@@ -0,0 +1,111 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import './field_button.scss';
+import classNames from 'classnames';
+import React, { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from 'react';
+import { CommonProps } from '@elastic/eui';
+
+export interface FieldButtonProps extends HTMLAttributes {
+ /**
+ * Label for the button
+ */
+ fieldName: ReactNode;
+ /**
+ * Icon representing the field type.
+ * Recommend using FieldIcon
+ */
+ fieldIcon?: ReactNode;
+ /**
+ * An optional node to place inside and at the end of the
+ */
+ fieldInfoIcon?: ReactNode;
+ /**
+ * An optional node to place outside of and to the right of the
+ */
+ fieldAction?: ReactNode;
+ /**
+ * Adds a forced focus ring to the whole component
+ */
+ isActive?: boolean;
+ /**
+ * Styles the component differently to indicate it is draggable
+ */
+ isDraggable?: boolean;
+ /**
+ * Use the small size in condensed areas
+ */
+ size?: ButtonSize;
+ className?: string;
+ /**
+ * The component always renders a `` and therefore will always need an `onClick`
+ */
+ onClick: () => void;
+ /**
+ * Pass more button props to the actual `` element
+ */
+ buttonProps?: ButtonHTMLAttributes & CommonProps;
+}
+
+const sizeToClassNameMap = {
+ s: 'kbnFieldButton--small',
+ m: null,
+} as const;
+
+export type ButtonSize = keyof typeof sizeToClassNameMap;
+
+export const SIZES = Object.keys(sizeToClassNameMap) as ButtonSize[];
+
+export function FieldButton({
+ size = 'm',
+ isActive = false,
+ fieldIcon,
+ fieldName,
+ fieldInfoIcon,
+ fieldAction,
+ className,
+ isDraggable = false,
+ onClick,
+ buttonProps,
+ ...rest
+}: FieldButtonProps) {
+ const classes = classNames(
+ 'kbnFieldButton',
+ size ? sizeToClassNameMap[size] : null,
+ { 'kbnFieldButton-isActive': isActive },
+ { 'kbnFieldButton--isDraggable': isDraggable },
+ className
+ );
+
+ const buttonClasses = classNames(
+ 'kbn-resetFocusState kbnFieldButton__button',
+ buttonProps && buttonProps.className
+ );
+
+ return (
+
+
+ {fieldIcon && {fieldIcon} }
+ {fieldName && {fieldName} }
+ {fieldInfoIcon && {fieldInfoIcon}
}
+
+ {fieldAction &&
{fieldAction}
}
+
+ );
+}
diff --git a/src/plugins/kibana_react/public/field_button/index.ts b/src/plugins/kibana_react/public/field_button/index.ts
new file mode 100644
index 0000000000000..4819a7623a163
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+export * from './field_button';
diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts
index 7f8bf6c04cecc..34140703fd8ae 100644
--- a/src/plugins/kibana_react/public/index.ts
+++ b/src/plugins/kibana_react/public/index.ts
@@ -23,6 +23,7 @@ export * from './context';
export * from './overlays';
export * from './ui_settings';
export * from './field_icon';
+export * from './field_button';
export * from './table_list_view';
export * from './split_panel';
export * from './react_router_navigate';
diff --git a/src/plugins/kibana_utils/public/state_management/url/format.ts b/src/plugins/kibana_utils/public/state_management/url/format.ts
index 2912b665ff014..4497e509bc86b 100644
--- a/src/plugins/kibana_utils/public/state_management/url/format.ts
+++ b/src/plugins/kibana_utils/public/state_management/url/format.ts
@@ -22,6 +22,23 @@ import { stringify, ParsedQuery } from 'query-string';
import { parseUrl, parseUrlHash } from './parse';
import { url as urlUtils } from '../../../common';
+export function replaceUrlQuery(
+ rawUrl: string,
+ queryReplacer: (query: ParsedQuery) => ParsedQuery
+) {
+ const url = parseUrl(rawUrl);
+ const newQuery = queryReplacer(url.query || {});
+ const searchQueryString = stringify(urlUtils.encodeQuery(newQuery), {
+ sort: false,
+ encode: false,
+ });
+ if (!url.search && !searchQueryString) return rawUrl; // nothing to change. return original url
+ return formatUrl({
+ ...url,
+ search: searchQueryString,
+ });
+}
+
export function replaceUrlHashQuery(
rawUrl: string,
queryReplacer: (query: ParsedQuery) => ParsedQuery
diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts
index a8c3aab2202d1..3d25134cd178d 100644
--- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts
+++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts
@@ -78,6 +78,27 @@ describe('kbn_url_storage', () => {
const retrievedState2 = getStateFromKbnUrl('_s', newUrl);
expect(retrievedState2).toEqual(state2);
});
+
+ it('should set query to url with storeInHashQuery: false', () => {
+ let newUrl = setStateToKbnUrl(
+ '_a',
+ { tab: 'other' },
+ { useHash: false, storeInHashQuery: false },
+ 'http://localhost:5601/oxf/app/kibana/yourApp'
+ );
+ expect(newUrl).toMatchInlineSnapshot(
+ `"http://localhost:5601/oxf/app/kibana/yourApp?_a=(tab:other)"`
+ );
+ newUrl = setStateToKbnUrl(
+ '_b',
+ { f: 'test', i: '', l: '' },
+ { useHash: false, storeInHashQuery: false },
+ newUrl
+ );
+ expect(newUrl).toMatchInlineSnapshot(
+ `"http://localhost:5601/oxf/app/kibana/yourApp?_a=(tab:other)&_b=(f:test,i:'',l:'')"`
+ );
+ });
});
describe('urlControls', () => {
diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts
index fefd5f668c6b3..a3b220f911504 100644
--- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts
+++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts
@@ -22,7 +22,7 @@ import { stringify } from 'query-string';
import { createBrowserHistory, History } from 'history';
import { decodeState, encodeState } from '../state_encoder';
import { getCurrentUrl, parseUrl, parseUrlHash } from './parse';
-import { replaceUrlHashQuery } from './format';
+import { replaceUrlHashQuery, replaceUrlQuery } from './format';
import { url as urlUtils } from '../../../common';
/**
@@ -84,10 +84,14 @@ export function getStateFromKbnUrl(
export function setStateToKbnUrl(
key: string,
state: State,
- { useHash = false }: { useHash: boolean } = { useHash: false },
+ { useHash = false, storeInHashQuery = true }: { useHash: boolean; storeInHashQuery?: boolean } = {
+ useHash: false,
+ storeInHashQuery: true,
+ },
rawUrl = window.location.href
): string {
- return replaceUrlHashQuery(rawUrl, (query) => {
+ const replacer = storeInHashQuery ? replaceUrlHashQuery : replaceUrlQuery;
+ return replacer(rawUrl, (query) => {
const encoded = encodeState(state, useHash);
return {
...query,
diff --git a/src/plugins/kibana_utils/public/ui/configurable.ts b/src/plugins/kibana_utils/public/ui/configurable.ts
index a4a9f09c1c0e0..3fa5cdc8b5e47 100644
--- a/src/plugins/kibana_utils/public/ui/configurable.ts
+++ b/src/plugins/kibana_utils/public/ui/configurable.ts
@@ -26,12 +26,12 @@ export interface Configurable
/**
* Create default config for this item, used when item is created for the first time.
*/
- readonly createConfig: () => Config;
+ readonly createConfig: (context: Context) => Config;
/**
* Is this config valid. Used to validate user's input before saving.
*/
- readonly isConfigValid: (config: Config) => boolean;
+ readonly isConfigValid: (config: Config, context: Context) => boolean;
/**
* `UiComponent` to be rendered when collecting configuration for this item.
diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts
index 6b9c7d1c52db9..14612ab1b2a57 100644
--- a/src/plugins/maps_legacy/public/index.ts
+++ b/src/plugins/maps_legacy/public/index.ts
@@ -48,6 +48,10 @@ export interface MapsLegacyConfigType {
includeElasticMapsService: boolean;
proxyElasticMapsServiceInMaps: boolean;
tilemap: any;
+ emsFontLibraryUrl: string;
+ emsFileApiUrl: string;
+ emsTileApiUrl: string;
+ emsLandingPageUrl: string;
}
export function plugin(initializerContext: PluginInitializerContext) {
diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx
index a1653c5289255..992a2fcf0c89d 100644
--- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx
+++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import { ButtonIconSide } from '@elastic/eui';
+import { EuiButtonProps } from '@elastic/eui';
export type TopNavMenuAction = (anchorElement: HTMLElement) => void;
@@ -32,7 +32,7 @@ export interface TopNavMenuData {
tooltip?: string | (() => string | undefined);
emphasize?: boolean;
iconType?: string;
- iconSide?: ButtonIconSide;
+ iconSide?: EuiButtonProps['iconSide'];
}
export interface RegisteredTopNavMenuData extends TopNavMenuData {
diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
index 1c5642f9b75b7..b058ef0de448b 100644
--- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
+++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
@@ -46,6 +46,7 @@ export function TopNavMenuItem(props: TopNavMenuData) {
iconType: props.iconType,
iconSide: props.iconSide,
'data-test-subj': props.testId,
+ className: props.className,
};
const btn = props.emphasize ? (
diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap
index d56776c2be9d7..f5c2d3efd56f7 100644
--- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap
+++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap
@@ -58,45 +58,63 @@ exports[`Intro component renders correctly 1`] = `
iconType="eye"
size="s"
>
-
-
-
-
-
-
-
+
- View search
-
-
-
-
+
+
+
+
+
+ View search
+
+
+
+
+
+
@@ -113,45 +131,64 @@ exports[`Intro component renders correctly 1`] = `
onClick={[Function]}
size="s"
>
-
-
-
-
-
-
-
+
- Delete search
-
-
-
-
+
+
+
+
+
+ Delete search
+
+
+
+
+
+
diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx
index 6256e5fcd49c5..0c7bf64ca011d 100644
--- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx
+++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx
@@ -19,7 +19,6 @@
import React from 'react';
import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers';
-// @ts-expect-error
import { findTestSubject } from '@elastic/eui/lib/test';
import { keys } from '@elastic/eui';
import { httpServiceMock } from '../../../../../../core/public/mocks';
diff --git a/src/plugins/tile_map/public/plugin.ts b/src/plugins/tile_map/public/plugin.ts
index 4582cd2283dc1..9a164f8a303f8 100644
--- a/src/plugins/tile_map/public/plugin.ts
+++ b/src/plugins/tile_map/public/plugin.ts
@@ -34,8 +34,7 @@ import { createTileMapFn } from './tile_map_fn';
import { createTileMapTypeDefinition } from './tile_map_type';
import { IServiceSettings, MapsLegacyPluginSetup } from '../../maps_legacy/public';
import { DataPublicPluginStart } from '../../data/public';
-import { setFormatService, setQueryService } from './services';
-import { setKibanaLegacy } from './services';
+import { setFormatService, setQueryService, setKibanaLegacy } from './services';
import { KibanaLegacyStart } from '../../kibana_legacy/public';
export interface TileMapConfigType {
diff --git a/src/plugins/timelion/public/app.js b/src/plugins/timelion/public/app.js
index 614a7539de44c..40fffe7a5a063 100644
--- a/src/plugins/timelion/public/app.js
+++ b/src/plugins/timelion/public/app.js
@@ -43,6 +43,7 @@ import { initTimelionOptionsSheetDirective } from './directives/timelion_options
import { initSavedObjectSaveAsCheckBoxDirective } from './directives/saved_object_save_as_checkbox';
import { initSavedObjectFinderDirective } from './directives/saved_object_finder';
import { initTimelionTabsDirective } from './components/timelionhelp_tabs_directive';
+import { initTimelionTDeprecationDirective } from './components/timelion_deprecation_directive';
import { initInputFocusDirective } from './directives/input_focus';
import { Chart } from './directives/chart/chart';
import { TimelionInterval } from './directives/timelion_interval/timelion_interval';
@@ -84,6 +85,7 @@ export function initTimelionApp(app, deps) {
initTimelionHelpDirective(app);
initInputFocusDirective(app);
initTimelionTabsDirective(app, deps);
+ initTimelionTDeprecationDirective(app, deps);
initSavedObjectFinderDirective(app, savedSheetLoader, deps.core.uiSettings);
initSavedObjectSaveAsCheckBoxDirective(app);
initCellsDirective(app);
diff --git a/src/plugins/timelion/public/components/timelion_deprecation.tsx b/src/plugins/timelion/public/components/timelion_deprecation.tsx
new file mode 100644
index 0000000000000..f9f04d3504570
--- /dev/null
+++ b/src/plugins/timelion/public/components/timelion_deprecation.tsx
@@ -0,0 +1,52 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiSpacer, EuiCallOut, EuiLink } from '@elastic/eui';
+import React from 'react';
+import { DocLinksStart } from '../../../../core/public';
+
+export const TimelionDeprecation = ({ links }: DocLinksStart) => {
+ const timelionDeprecationLink = links.visualize.timelionDeprecation;
+ return (
+ <>
+
+
+
+ ),
+ }}
+ />
+ }
+ color="warning"
+ iconType="alert"
+ size="s"
+ />
+
+ >
+ );
+};
diff --git a/src/plugins/timelion/public/components/timelion_deprecation_directive.js b/src/plugins/timelion/public/components/timelion_deprecation_directive.js
new file mode 100644
index 0000000000000..6a38161c7d40d
--- /dev/null
+++ b/src/plugins/timelion/public/components/timelion_deprecation_directive.js
@@ -0,0 +1,41 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { TimelionDeprecation } from './timelion_deprecation';
+
+export function initTimelionTDeprecationDirective(app, deps) {
+ app.directive('timelionDeprecation', function (reactDirective) {
+ return reactDirective(
+ () => {
+ return (
+
+
+
+ );
+ },
+ [],
+ {
+ restrict: 'E',
+ scope: {
+ docLinks: '=',
+ },
+ }
+ );
+ });
+}
diff --git a/src/plugins/timelion/public/index.html b/src/plugins/timelion/public/index.html
index 54efae7f81ba7..0cf64287a3bd5 100644
--- a/src/plugins/timelion/public/index.html
+++ b/src/plugins/timelion/public/index.html
@@ -28,6 +28,7 @@
+
+
+ }
+ onActivation={[Function]}
+ onDeactivation={[Function]}
+ persistentFocus={false}
+ returnFocus={[Function]}
+ shards={Array []}
+ sideCar={
+ Object {
+ "assignMedium": [Function],
+ "assignSyncMedium": [Function],
+ "options": Object {
+ "async": true,
+ "ssr": false,
+ },
+ "read": [Function],
+ "useMedium": [Function],
+ }
+ }
+ >
+
+
+
+ }
+ onActivation={[Function]}
+ onDeactivation={[Function]}
+ persistentFocus={false}
+ returnFocus={[Function]}
+ shards={Array []}
+ >
+
+
-
-
+ }
+ onActivation={[Function]}
+ onDeactivation={[Function]}
+ persistentFocus={false}
+ returnFocus={[Function]}
+ shards={Array []}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
- Select a visualization type
+ New Visualization
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
+
- Start creating your visualization by selecting a type for that visualization.
-
-
+
+
+
+
+
+
+
+
+ 2 types found
+
+
+
+
+
+
+
+ Vis with alias Url
+
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onMouseEnter={[Function]}
+ onMouseLeave={[Function]}
+ role="menuitem"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vis with alias Url
+
+
+
+
+
+
+
+
+ Vis with search
+
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onMouseEnter={[Function]}
+ onMouseLeave={[Function]}
+ role="menuitem"
+ >
+
+
+
+
+
+ Vis with search
+
+
+
+
+
+
+
+
+ Vis Type 1
+
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onMouseEnter={[Function]}
+ onMouseLeave={[Function]}
+ role="menuitem"
+ >
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+ Select a visualization type
+
+
+
+
+
+
+
+
+
+
+
+ Start creating your visualization by selecting a type for that visualization.
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
-
+ >
+
+
+
+
+
+
+
+
+
+
@@ -1712,11 +2376,15 @@ exports[`NewVisModal should render as expected 1`] = `
class="euiOverlayMask euiOverlayMask--aboveHeader"
>
-
-
-
-
+ lockProps={
+ Object {
+ "allowPinchZoom": undefined,
+ "enabled": false,
+ "inert": undefined,
+ "onMouseDown": [Function],
+ "onTouchStart": [Function],
+ "shards": undefined,
+ "sideCar": [Function],
+ }
}
+ noFocusGuards={false}
onActivation={[Function]}
onDeactivation={[Function]}
persistentFocus={false}
+ returnFocus={true}
+ sideCar={[Function]}
>
-
+
+
+
+
+
+
+
+
+
+
+ Select a visualization type
+
+
+
+
+ Start creating your visualization by selecting a type for that visualization.
+
+
+
+
+
+
+
+
+ }
+ onActivation={[Function]}
+ onDeactivation={[Function]}
+ persistentFocus={false}
+ returnFocus={[Function]}
+ shards={Array []}
+ sideCar={
+ Object {
+ "assignMedium": [Function],
+ "assignSyncMedium": [Function],
+ "options": Object {
+ "async": true,
+ "ssr": false,
+ },
+ "read": [Function],
+ "useMedium": [Function],
+ }
+ }
+ >
+
+
-
-
- Select a visualization type
-
-
-
- Start creating your visualization by selecting a type for that visualization.
-
+
+ Select a visualization type
+
+
+
+
+ Start creating your visualization by selecting a type for that visualization.
+
+
-
- }
- onActivation={[Function]}
- onDeactivation={[Function]}
- persistentFocus={false}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
- New Visualization
-
-
-
-
-
-
-
-
+
+
+ Select a visualization type
+
+
-
-
-
-
+ Start creating your visualization by selecting a type for that visualization.
+
+
+
+
+
+
+
+
+ }
+ onActivation={[Function]}
+ onDeactivation={[Function]}
+ persistentFocus={false}
+ returnFocus={[Function]}
+ shards={Array []}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ Select a visualization type
+
+
+
+
+ Start creating your visualization by selecting a type for that visualization.
+
+
-
+
-
+
-
-
+ }
+ onActivation={[Function]}
+ onDeactivation={[Function]}
+ persistentFocus={false}
+ returnFocus={[Function]}
+ shards={Array []}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
- Select a visualization type
+ New Visualization
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
+
- Start creating your visualization by selecting a type for that visualization.
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Vis Type 1
+
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onMouseEnter={[Function]}
+ onMouseLeave={[Function]}
+ role="menuitem"
+ >
+
+
+
+
+
+
+
+ Vis with alias Url
+
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onMouseEnter={[Function]}
+ onMouseLeave={[Function]}
+ role="menuitem"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vis with alias Url
+
+
+
+
+
+
+
+
+ Vis with search
+
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onMouseEnter={[Function]}
+ onMouseLeave={[Function]}
+ role="menuitem"
+ >
+
+
+
+
+
+ Vis with search
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+ Select a visualization type
+
+
+
+
+
+
+
+
+
+
+
+ Start creating your visualization by selecting a type for that visualization.
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
-
+ >
+
+
+
+
+
+
+
+
+
+
diff --git a/src/plugins/visualize/common/constants.ts b/src/plugins/visualize/common/constants.ts
new file mode 100644
index 0000000000000..4e33638286a19
--- /dev/null
+++ b/src/plugins/visualize/common/constants.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export const AGGS_TERMS_SIZE_SETTING = 'discover:aggs:terms:size';
diff --git a/src/plugins/visualize/kibana.json b/src/plugins/visualize/kibana.json
index 520d1e1daa6fe..a6cc8d8f8af60 100644
--- a/src/plugins/visualize/kibana.json
+++ b/src/plugins/visualize/kibana.json
@@ -9,7 +9,8 @@
"navigation",
"savedObjects",
"visualizations",
- "embeddable"
+ "embeddable",
+ "uiActions"
],
"optionalPlugins": ["home", "share"],
"requiredBundles": [
diff --git a/src/plugins/visualize/public/actions/visualize_field_action.ts b/src/plugins/visualize/public/actions/visualize_field_action.ts
new file mode 100644
index 0000000000000..6671d2c981910
--- /dev/null
+++ b/src/plugins/visualize/public/actions/visualize_field_action.ts
@@ -0,0 +1,97 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { i18n } from '@kbn/i18n';
+import {
+ createAction,
+ ACTION_VISUALIZE_FIELD,
+ VisualizeFieldContext,
+} from '../../../ui_actions/public';
+import {
+ getApplication,
+ getUISettings,
+ getIndexPatterns,
+ getQueryService,
+ getShareService,
+} from '../services';
+import { VISUALIZE_APP_URL_GENERATOR, VisualizeUrlGeneratorState } from '../url_generator';
+import { AGGS_TERMS_SIZE_SETTING } from '../../common/constants';
+
+export const visualizeFieldAction = createAction
({
+ type: ACTION_VISUALIZE_FIELD,
+ getDisplayName: () =>
+ i18n.translate('visualize.discover.visualizeFieldLabel', {
+ defaultMessage: 'Visualize field',
+ }),
+ isCompatible: async () => !!getApplication().capabilities.visualize.show,
+ getHref: async (context) => {
+ const url = await getVisualizeUrl(context);
+ return url;
+ },
+ execute: async (context) => {
+ const url = await getVisualizeUrl(context);
+ const hash = url.split('#')[1];
+
+ getApplication().navigateToApp('visualize', {
+ path: `/#${hash}`,
+ });
+ },
+});
+
+const getVisualizeUrl = async (context: VisualizeFieldContext) => {
+ const indexPattern = await getIndexPatterns().get(context.indexPatternId);
+ const field = indexPattern.fields.find((fld) => fld.name === context.fieldName);
+ const aggsTermSize = getUISettings().get(AGGS_TERMS_SIZE_SETTING);
+ let agg;
+
+ // If we're visualizing a date field, and our index is time based (and thus has a time filter),
+ // then run a date histogram
+ if (field?.type === 'date' && indexPattern.timeFieldName === context.fieldName) {
+ agg = {
+ type: 'date_histogram',
+ schema: 'segment',
+ params: {
+ field: context.fieldName,
+ interval: 'auto',
+ },
+ };
+ } else {
+ agg = {
+ type: 'terms',
+ schema: 'segment',
+ params: {
+ field: context.fieldName,
+ size: parseInt(aggsTermSize, 10),
+ orderBy: '1',
+ },
+ };
+ }
+ const generator = getShareService().urlGenerators.getUrlGenerator(VISUALIZE_APP_URL_GENERATOR);
+ const urlState: VisualizeUrlGeneratorState = {
+ filters: getQueryService().filterManager.getFilters(),
+ query: getQueryService().queryString.getQuery(),
+ timeRange: getQueryService().timefilter.timefilter.getTime(),
+ indexPatternId: context.indexPatternId,
+ type: 'histogram',
+ vis: {
+ type: 'histogram',
+ aggs: [{ schema: 'metric', type: 'count', id: '1' }, agg],
+ },
+ };
+ return generator.createUrl(urlState);
+};
diff --git a/src/plugins/visualize/public/application/app.tsx b/src/plugins/visualize/public/application/app.tsx
index 0e71d72a3d4c7..8dd6b2ace8413 100644
--- a/src/plugins/visualize/public/application/app.tsx
+++ b/src/plugins/visualize/public/application/app.tsx
@@ -24,7 +24,12 @@ import { Route, Switch, useLocation } from 'react-router-dom';
import { syncQueryStateWithUrl } from '../../../data/public';
import { useKibana } from '../../../kibana_react/public';
import { VisualizeServices } from './types';
-import { VisualizeEditor, VisualizeListing, VisualizeNoMatch } from './components';
+import {
+ VisualizeEditor,
+ VisualizeListing,
+ VisualizeNoMatch,
+ VisualizeByValueEditor,
+} from './components';
import { VisualizeConstants } from './visualize_constants';
export const VisualizeApp = () => {
@@ -48,6 +53,9 @@ export const VisualizeApp = () => {
return (
+
+
+
diff --git a/src/plugins/visualize/public/application/components/index.ts b/src/plugins/visualize/public/application/components/index.ts
index a3a7fde1d6569..1666bae9b72e0 100644
--- a/src/plugins/visualize/public/application/components/index.ts
+++ b/src/plugins/visualize/public/application/components/index.ts
@@ -20,3 +20,4 @@
export { VisualizeListing } from './visualize_listing';
export { VisualizeEditor } from './visualize_editor';
export { VisualizeNoMatch } from './visualize_no_match';
+export { VisualizeByValueEditor } from './visualize_byvalue_editor';
diff --git a/src/plugins/visualize/public/application/components/visualize_byvalue_editor.tsx b/src/plugins/visualize/public/application/components/visualize_byvalue_editor.tsx
new file mode 100644
index 0000000000000..a78633d6841e5
--- /dev/null
+++ b/src/plugins/visualize/public/application/components/visualize_byvalue_editor.tsx
@@ -0,0 +1,105 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import './visualize_editor.scss';
+import React, { useEffect, useState } from 'react';
+import { EventEmitter } from 'events';
+
+import { VisualizeInput } from 'src/plugins/visualizations/public';
+import { useKibana } from '../../../../kibana_react/public';
+import {
+ useChromeVisibility,
+ useVisByValue,
+ useVisualizeAppState,
+ useEditorUpdates,
+ useLinkedSearchUpdates,
+} from '../utils';
+import { VisualizeServices } from '../types';
+import { VisualizeEditorCommon } from './visualize_editor_common';
+
+export const VisualizeByValueEditor = () => {
+ const [originatingApp, setOriginatingApp] = useState();
+ const { services } = useKibana();
+ const [eventEmitter] = useState(new EventEmitter());
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(true);
+ const [embeddableId, setEmbeddableId] = useState();
+ const [valueInput, setValueInput] = useState();
+
+ useEffect(() => {
+ const { originatingApp: value, embeddableId: embeddableIdValue, valueInput: valueInputValue } =
+ services.embeddable
+ .getStateTransfer(services.scopedHistory)
+ .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'embeddableId', 'valueInput'] }) ||
+ {};
+ setOriginatingApp(value);
+ setValueInput(valueInputValue);
+ setEmbeddableId(embeddableIdValue);
+ if (!valueInputValue) {
+ history.back();
+ }
+ }, [services]);
+
+ const isChromeVisible = useChromeVisibility(services.chrome);
+
+ const { byValueVisInstance, visEditorRef, visEditorController } = useVisByValue(
+ services,
+ eventEmitter,
+ isChromeVisible,
+ valueInput,
+ originatingApp
+ );
+ const { appState, hasUnappliedChanges } = useVisualizeAppState(
+ services,
+ eventEmitter,
+ byValueVisInstance
+ );
+ const { isEmbeddableRendered, currentAppState } = useEditorUpdates(
+ services,
+ eventEmitter,
+ setHasUnsavedChanges,
+ appState,
+ byValueVisInstance,
+ visEditorController
+ );
+ useLinkedSearchUpdates(services, eventEmitter, appState, byValueVisInstance);
+
+ useEffect(() => {
+ // clean up all registered listeners if any is left
+ return () => {
+ eventEmitter.removeAllListeners();
+ };
+ }, [eventEmitter]);
+
+ return (
+
+ );
+};
diff --git a/src/plugins/visualize/public/application/components/visualize_editor.tsx b/src/plugins/visualize/public/application/components/visualize_editor.tsx
index 516dcacfe5813..0bf5b26e1339f 100644
--- a/src/plugins/visualize/public/application/components/visualize_editor.tsx
+++ b/src/plugins/visualize/public/application/components/visualize_editor.tsx
@@ -21,8 +21,6 @@ import './visualize_editor.scss';
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { EventEmitter } from 'events';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiScreenReaderOnly } from '@elastic/eui';
import { useKibana } from '../../../../kibana_react/public';
import {
@@ -33,8 +31,7 @@ import {
useLinkedSearchUpdates,
} from '../utils';
import { VisualizeServices } from '../types';
-import { ExperimentalVisInfo } from './experimental_vis_info';
-import { VisualizeTopNav } from './visualize_top_nav';
+import { VisualizeEditorCommon } from './visualize_editor_common';
export const VisualizeEditor = () => {
const { id: visualizationIdFromUrl } = useParams();
@@ -67,7 +64,9 @@ export const VisualizeEditor = () => {
useEffect(() => {
const { originatingApp: value } =
- services.embeddable.getStateTransfer(services.scopedHistory).getIncomingEditorState() || {};
+ services.embeddable
+ .getStateTransfer(services.scopedHistory)
+ .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'input'] }) || {};
setOriginatingApp(value);
}, [services]);
@@ -79,38 +78,19 @@ export const VisualizeEditor = () => {
}, [eventEmitter]);
return (
-
- {savedVisInstance && appState && currentAppState && (
-
- )}
- {savedVisInstance?.vis?.type?.isExperimental &&
}
- {savedVisInstance && (
-
-
-
-
-
- )}
-
-
+
);
};
diff --git a/src/plugins/visualize/public/application/components/visualize_editor_common.tsx b/src/plugins/visualize/public/application/components/visualize_editor_common.tsx
new file mode 100644
index 0000000000000..b811936c63b14
--- /dev/null
+++ b/src/plugins/visualize/public/application/components/visualize_editor_common.tsx
@@ -0,0 +1,110 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import './visualize_editor.scss';
+import React, { RefObject } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiScreenReaderOnly } from '@elastic/eui';
+import { VisualizeTopNav } from './visualize_top_nav';
+import { ExperimentalVisInfo } from './experimental_vis_info';
+import {
+ SavedVisInstance,
+ VisualizeAppState,
+ VisualizeAppStateContainer,
+ VisualizeEditorVisInstance,
+} from '../types';
+
+interface VisualizeEditorCommonProps {
+ visInstance?: VisualizeEditorVisInstance;
+ appState: VisualizeAppStateContainer | null;
+ currentAppState?: VisualizeAppState;
+ isChromeVisible?: boolean;
+ hasUnsavedChanges: boolean;
+ setHasUnsavedChanges: (value: boolean) => void;
+ hasUnappliedChanges: boolean;
+ isEmbeddableRendered: boolean;
+ visEditorRef: RefObject;
+ originatingApp?: string;
+ setOriginatingApp?: (originatingApp: string | undefined) => void;
+ visualizationIdFromUrl?: string;
+ embeddableId?: string;
+}
+
+export const VisualizeEditorCommon = ({
+ visInstance,
+ appState,
+ currentAppState,
+ isChromeVisible,
+ hasUnsavedChanges,
+ setHasUnsavedChanges,
+ hasUnappliedChanges,
+ isEmbeddableRendered,
+ originatingApp,
+ setOriginatingApp,
+ visualizationIdFromUrl,
+ embeddableId,
+ visEditorRef,
+}: VisualizeEditorCommonProps) => {
+ return (
+
+ {visInstance && appState && currentAppState && (
+
+ )}
+ {visInstance?.vis?.type?.isExperimental &&
}
+ {visInstance && (
+
+
+ {'savedVis' in visInstance && visInstance.savedVis.id ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+
+
+ );
+};
diff --git a/src/plugins/visualize/public/application/components/visualize_top_nav.tsx b/src/plugins/visualize/public/application/components/visualize_top_nav.tsx
index f2cb2d49f59b0..12a3d1cdf95b1 100644
--- a/src/plugins/visualize/public/application/components/visualize_top_nav.tsx
+++ b/src/plugins/visualize/public/application/components/visualize_top_nav.tsx
@@ -25,7 +25,7 @@ import {
VisualizeServices,
VisualizeAppState,
VisualizeAppStateContainer,
- SavedVisInstance,
+ VisualizeEditorVisInstance,
} from '../types';
import { APP_NAME } from '../visualize_constants';
import { getTopNavConfig } from '../utils';
@@ -38,10 +38,11 @@ interface VisualizeTopNavProps {
setHasUnsavedChanges: (value: boolean) => void;
hasUnappliedChanges: boolean;
originatingApp?: string;
+ visInstance: VisualizeEditorVisInstance;
setOriginatingApp?: (originatingApp: string | undefined) => void;
- savedVisInstance: SavedVisInstance;
stateContainer: VisualizeAppStateContainer;
visualizationIdFromUrl?: string;
+ embeddableId?: string;
}
const TopNav = ({
@@ -53,26 +54,26 @@ const TopNav = ({
hasUnappliedChanges,
originatingApp,
setOriginatingApp,
- savedVisInstance,
+ visInstance,
stateContainer,
visualizationIdFromUrl,
+ embeddableId,
}: VisualizeTopNavProps) => {
const { services } = useKibana();
const { TopNavMenu } = services.navigation.ui;
- const { embeddableHandler, vis } = savedVisInstance;
+ const { embeddableHandler, vis } = visInstance;
const [inspectorSession, setInspectorSession] = useState();
const openInspector = useCallback(() => {
const session = embeddableHandler.openInspector();
setInspectorSession(session);
}, [embeddableHandler]);
-
const handleRefresh = useCallback(
(_payload: any, isUpdate?: boolean) => {
if (isUpdate === false) {
- savedVisInstance.embeddableHandler.reload();
+ visInstance.embeddableHandler.reload();
}
},
- [savedVisInstance.embeddableHandler]
+ [visInstance.embeddableHandler]
);
const config = useMemo(() => {
@@ -85,9 +86,10 @@ const TopNav = ({
openInspector,
originatingApp,
setOriginatingApp,
- savedVisInstance,
+ visInstance,
stateContainer,
visualizationIdFromUrl,
+ embeddableId,
},
services
);
@@ -99,11 +101,12 @@ const TopNav = ({
hasUnappliedChanges,
openInspector,
originatingApp,
+ visInstance,
setOriginatingApp,
- savedVisInstance,
stateContainer,
visualizationIdFromUrl,
services,
+ embeddableId,
]);
const [indexPattern, setIndexPattern] = useState(vis.data.indexPattern);
const showDatePicker = () => {
diff --git a/src/plugins/visualize/public/application/types.ts b/src/plugins/visualize/public/application/types.ts
index 02ae1cc155dd2..65b88485b2f06 100644
--- a/src/plugins/visualize/public/application/types.ts
+++ b/src/plugins/visualize/public/application/types.ts
@@ -121,6 +121,14 @@ export interface SavedVisInstance {
embeddableHandler: VisualizeEmbeddableContract;
}
+export interface ByValueVisInstance {
+ vis: Vis;
+ savedSearch?: SavedObject;
+ embeddableHandler: VisualizeEmbeddableContract;
+}
+
+export type VisualizeEditorVisInstance = SavedVisInstance | ByValueVisInstance;
+
export interface IEditorController {
render(props: EditorRenderProps): void;
destroy(): void;
diff --git a/src/plugins/visualize/public/application/utils/breadcrumbs.ts b/src/plugins/visualize/public/application/utils/breadcrumbs.ts
index a1e5a9e8912e1..a5c246c539c54 100644
--- a/src/plugins/visualize/public/application/utils/breadcrumbs.ts
+++ b/src/plugins/visualize/public/application/utils/breadcrumbs.ts
@@ -21,6 +21,18 @@ import { i18n } from '@kbn/i18n';
import { VisualizeConstants } from '../visualize_constants';
+const appPrefixes: Record = {
+ dashboards: {
+ text: i18n.translate('visualize.dashboard.prefix.breadcrumb', {
+ defaultMessage: 'Dashboard',
+ }),
+ },
+};
+
+const defaultEditText = i18n.translate('visualize.editor.defaultEditBreadcrumbText', {
+ defaultMessage: 'Edit',
+});
+
export function getLandingBreadcrumbs() {
return [
{
@@ -43,7 +55,12 @@ export function getCreateBreadcrumbs() {
];
}
-export function getEditBreadcrumbs(text: string) {
+export function getBreadcrumbsPrefixedWithApp(originatingApp: string) {
+ const originatingAppBreadcrumb = appPrefixes[originatingApp];
+ return [originatingAppBreadcrumb, ...getLandingBreadcrumbs(), { text: defaultEditText }];
+}
+
+export function getEditBreadcrumbs(text: string = defaultEditText) {
return [
...getLandingBreadcrumbs(),
{
diff --git a/src/plugins/visualize/public/application/utils/create_visualize_app_state.ts b/src/plugins/visualize/public/application/utils/create_visualize_app_state.ts
index 52b7e3ede298b..7a7e04d78354b 100644
--- a/src/plugins/visualize/public/application/utils/create_visualize_app_state.ts
+++ b/src/plugins/visualize/public/application/utils/create_visualize_app_state.ts
@@ -32,6 +32,7 @@ const STATE_STORAGE_KEY = '_a';
interface Arguments {
kbnUrlStateStorage: IKbnUrlStateStorage;
stateDefaults: VisualizeAppState;
+ byValue?: boolean;
}
function toObject(state: PureVisState): PureVisState {
@@ -40,55 +41,67 @@ function toObject(state: PureVisState): PureVisState {
}) as PureVisState;
}
-export function createVisualizeAppState({ stateDefaults, kbnUrlStateStorage }: Arguments) {
+const pureTransitions = {
+ set: (state) => (prop, value) => ({ ...state, [prop]: value }),
+ setVis: (state) => (vis) => ({
+ ...state,
+ vis: {
+ ...state.vis,
+ ...vis,
+ },
+ }),
+ unlinkSavedSearch: (state) => ({ query, parentFilters = [] }) => ({
+ ...state,
+ query: query || state.query,
+ filters: union(state.filters, parentFilters),
+ linked: false,
+ }),
+ updateVisState: (state) => (newVisState) => ({ ...state, vis: toObject(newVisState) }),
+ updateSavedQuery: (state) => (savedQueryId) => {
+ const updatedState = {
+ ...state,
+ savedQuery: savedQueryId,
+ };
+
+ if (!savedQueryId) {
+ delete updatedState.savedQuery;
+ }
+
+ return updatedState;
+ },
+} as VisualizeAppStateTransitions;
+
+function createVisualizeByValueAppState(stateDefaults: VisualizeAppState) {
+ const initialState = migrateAppState({
+ ...stateDefaults,
+ ...stateDefaults,
+ });
+ const stateContainer = createStateContainer(
+ initialState,
+ pureTransitions
+ );
+ const stopStateSync = () => {};
+ return { stateContainer, stopStateSync };
+}
+
+function createDefaultVisualizeAppState({ stateDefaults, kbnUrlStateStorage }: Arguments) {
const urlState = kbnUrlStateStorage.get(STATE_STORAGE_KEY);
const initialState = migrateAppState({
...stateDefaults,
...urlState,
});
-
/*
- make sure url ('_a') matches initial state
- Initializing appState does two things - first it translates the defaults into AppState,
- second it updates appState based on the url (the url trumps the defaults). This means if
- we update the state format at all and want to handle BWC, we must not only migrate the
- data stored with saved vis, but also any old state in the url.
- */
+ make sure url ('_a') matches initial state
+ Initializing appState does two things - first it translates the defaults into AppState,
+ second it updates appState based on the url (the url trumps the defaults). This means if
+ we update the state format at all and want to handle BWC, we must not only migrate the
+ data stored with saved vis, but also any old state in the url.
+ */
kbnUrlStateStorage.set(STATE_STORAGE_KEY, initialState, { replace: true });
-
const stateContainer = createStateContainer(
initialState,
- {
- set: (state) => (prop, value) => ({ ...state, [prop]: value }),
- setVis: (state) => (vis) => ({
- ...state,
- vis: {
- ...state.vis,
- ...vis,
- },
- }),
- unlinkSavedSearch: (state) => ({ query, parentFilters = [] }) => ({
- ...state,
- query: query || state.query,
- filters: union(state.filters, parentFilters),
- linked: false,
- }),
- updateVisState: (state) => (newVisState) => ({ ...state, vis: toObject(newVisState) }),
- updateSavedQuery: (state) => (savedQueryId) => {
- const updatedState = {
- ...state,
- savedQuery: savedQueryId,
- };
-
- if (!savedQueryId) {
- delete updatedState.savedQuery;
- }
-
- return updatedState;
- },
- }
+ pureTransitions
);
-
const { start: startStateSync, stop: stopStateSync } = syncState({
storageKey: STATE_STORAGE_KEY,
stateContainer: {
@@ -102,9 +115,14 @@ export function createVisualizeAppState({ stateDefaults, kbnUrlStateStorage }: A
},
stateStorage: kbnUrlStateStorage,
});
-
// start syncing the appState with the ('_a') url
startStateSync();
-
return { stateContainer, stopStateSync };
}
+
+export function createVisualizeAppState({ stateDefaults, kbnUrlStateStorage, byValue }: Arguments) {
+ if (byValue) {
+ return createVisualizeByValueAppState(stateDefaults);
+ }
+ return createDefaultVisualizeAppState({ stateDefaults, kbnUrlStateStorage });
+}
diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx
index da9ba66a914dd..87a6437192aa9 100644
--- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx
+++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx
@@ -31,9 +31,14 @@ import {
} from '../../../../saved_objects/public';
import { unhashUrl } from '../../../../kibana_utils/public';
-import { SavedVisInstance, VisualizeServices, VisualizeAppStateContainer } from '../types';
+import {
+ VisualizeServices,
+ VisualizeAppStateContainer,
+ VisualizeEditorVisInstance,
+} from '../types';
import { VisualizeConstants } from '../visualize_constants';
import { getEditBreadcrumbs } from './breadcrumbs';
+
interface TopNavConfigParams {
hasUnsavedChanges: boolean;
setHasUnsavedChanges: (value: boolean) => void;
@@ -41,9 +46,10 @@ interface TopNavConfigParams {
originatingApp?: string;
setOriginatingApp?: (originatingApp: string | undefined) => void;
hasUnappliedChanges: boolean;
- savedVisInstance: SavedVisInstance;
+ visInstance: VisualizeEditorVisInstance;
stateContainer: VisualizeAppStateContainer;
visualizationIdFromUrl?: string;
+ embeddableId?: string;
}
export const getTopNavConfig = (
@@ -54,9 +60,10 @@ export const getTopNavConfig = (
originatingApp,
setOriginatingApp,
hasUnappliedChanges,
- savedVisInstance: { embeddableHandler, savedVis, vis },
+ visInstance,
stateContainer,
visualizationIdFromUrl,
+ embeddableId,
}: TopNavConfigParams,
{
application,
@@ -71,10 +78,15 @@ export const getTopNavConfig = (
featureFlagConfig,
}: VisualizeServices
) => {
+ const { vis, embeddableHandler } = visInstance;
+ const savedVis = 'savedVis' in visInstance ? visInstance.savedVis : undefined;
/**
* Called when the user clicks "Save" button.
*/
async function doSave(saveOptions: SavedObjectSaveOpts) {
+ if (!savedVis) {
+ return {};
+ }
const newlyCreated = !Boolean(savedVis.id) || savedVis.copyOnSave;
// vis.title was not bound and it's needed to reflect title into visState
stateContainer.transitions.setVis({
@@ -147,8 +159,26 @@ export const getTopNavConfig = (
}
}
+ const createVisReference = () => {
+ if (!originatingApp) {
+ return;
+ }
+ const state = {
+ input: {
+ ...vis.serialize(),
+ id: embeddableId ? embeddableId : uuid.v4(),
+ },
+ type: VISUALIZE_EMBEDDABLE_TYPE,
+ embeddableId: '',
+ };
+ if (embeddableId) {
+ state.embeddableId = embeddableId;
+ }
+ embeddable.getStateTransfer().navigateToWithEmbeddablePackage(originatingApp, { state });
+ };
+
const topNavMenu: TopNavMenuData[] = [
- ...(originatingApp && savedVis.id
+ ...(originatingApp && ((savedVis && savedVis.id) || embeddableId)
? [
{
id: 'saveAndReturn',
@@ -180,27 +210,35 @@ export const getTopNavConfig = (
confirmOverwrite: false,
returnToOrigin: true,
};
+ if (
+ originatingApp === 'dashboards' &&
+ featureFlagConfig.showNewVisualizeFlow &&
+ !savedVis
+ ) {
+ return createVisReference();
+ }
return doSave(saveOptions);
},
},
]
: []),
- ...(visualizeCapabilities.save
+ ...(visualizeCapabilities.save && !embeddableId
? [
{
id: 'save',
label:
- savedVis.id && originatingApp
+ savedVis?.id && originatingApp
? i18n.translate('visualize.topNavMenu.saveVisualizationAsButtonLabel', {
defaultMessage: 'save as',
})
: i18n.translate('visualize.topNavMenu.saveVisualizationButtonLabel', {
defaultMessage: 'save',
}),
- emphasize: !savedVis.id || !originatingApp,
+ emphasize: (savedVis && !savedVis.id) || !originatingApp,
description: i18n.translate('visualize.topNavMenu.saveVisualizationButtonAriaLabel', {
defaultMessage: 'Save Visualization',
}),
+ className: savedVis?.id && originatingApp ? 'saveAsButton' : '',
testId: 'visualizeSaveButton',
disableButton: hasUnappliedChanges,
tooltip() {
@@ -213,7 +251,7 @@ export const getTopNavConfig = (
);
}
},
- run: () => {
+ run: (anchorElement: HTMLElement) => {
const onSave = async ({
newTitle,
newCopyOnSave,
@@ -222,6 +260,9 @@ export const getTopNavConfig = (
newDescription,
returnToOrigin,
}: OnSaveProps & { returnToOrigin: boolean }) => {
+ if (!savedVis) {
+ return;
+ }
const currentTitle = savedVis.title;
savedVis.title = newTitle;
savedVis.copyOnSave = newCopyOnSave;
@@ -239,32 +280,23 @@ export const getTopNavConfig = (
}
return response;
};
-
- const createVisReference = () => {
- if (!originatingApp) {
- return;
- }
- const input = {
- ...vis.serialize(),
- id: uuid.v4(),
- };
- embeddable.getStateTransfer().navigateToWithEmbeddablePackage(originatingApp, {
- state: { input, type: VISUALIZE_EMBEDDABLE_TYPE },
- });
- };
-
const saveModal = (
{}}
originatingApp={originatingApp}
/>
);
- if (originatingApp === 'dashboards' && featureFlagConfig.showNewVisualizeFlow) {
+ const isSaveAsButton = anchorElement.classList.contains('saveAsButton');
+ if (
+ originatingApp === 'dashboards' &&
+ featureFlagConfig.showNewVisualizeFlow &&
+ !isSaveAsButton
+ ) {
createVisReference();
- } else {
+ } else if (savedVis) {
showSaveModal(saveModal, I18nContext);
}
},
@@ -281,23 +313,24 @@ export const getTopNavConfig = (
}),
testId: 'shareTopNavButton',
run: (anchorElement) => {
- if (share) {
+ if (share && !embeddableId) {
+ // TODO: support sharing in by-value mode
share.toggleShareContextMenu({
anchorElement,
allowEmbed: true,
allowShortUrl: visualizeCapabilities.createShortUrl,
shareableUrl: unhashUrl(window.location.href),
- objectId: savedVis.id,
+ objectId: savedVis?.id,
objectType: 'visualization',
sharingData: {
- title: savedVis.title,
+ title: savedVis?.title,
},
isDirty: hasUnappliedChanges || hasUnsavedChanges,
});
}
},
// disable the Share button if no action specified
- disableButton: !share,
+ disableButton: !share || !!embeddableId,
},
{
id: 'inspector',
diff --git a/src/plugins/visualize/public/application/utils/get_visualization_instance.ts b/src/plugins/visualize/public/application/utils/get_visualization_instance.ts
index a75c84cf0b71c..3ffca578f8052 100644
--- a/src/plugins/visualize/public/application/utils/get_visualization_instance.ts
+++ b/src/plugins/visualize/public/application/utils/get_visualization_instance.ts
@@ -18,46 +18,31 @@
*/
import { i18n } from '@kbn/i18n';
-import { VisSavedObject, VisualizeEmbeddableContract } from 'src/plugins/visualizations/public';
+import {
+ SerializedVis,
+ Vis,
+ VisSavedObject,
+ VisualizeEmbeddableContract,
+ VisualizeInput,
+} from 'src/plugins/visualizations/public';
import { SearchSourceFields } from 'src/plugins/data/public';
import { SavedObject } from 'src/plugins/saved_objects/public';
+import { cloneDeep } from 'lodash';
import { createSavedSearchesLoader } from '../../../../discover/public';
import { VisualizeServices } from '../types';
-export const getVisualizationInstance = async (
- {
+const createVisualizeEmbeddableAndLinkSavedSearch = async (
+ vis: Vis,
+ visualizeServices: VisualizeServices
+) => {
+ const {
chrome,
data,
overlays,
- visualizations,
createVisEmbeddableFromObject,
savedObjects,
- savedVisualizations,
toastNotifications,
- }: VisualizeServices,
- /**
- * opts can be either a saved visualization id passed as string,
- * or an object of new visualization params.
- * Both come from url search query
- */
- opts?: Record | string
-) => {
- const savedVis: VisSavedObject = await savedVisualizations.get(opts);
-
- if (typeof opts !== 'string') {
- savedVis.searchSourceFields = { index: opts?.indexPattern } as SearchSourceFields;
- }
- const serializedVis = visualizations.convertToSerializedVis(savedVis);
- let vis = await visualizations.createVis(serializedVis.type, serializedVis);
-
- if (vis.type.setup) {
- try {
- vis = await vis.type.setup(vis);
- } catch {
- // skip this catch block
- }
- }
-
+ } = visualizeServices;
const embeddableHandler = (await createVisEmbeddableFromObject(vis, {
timeRange: data.query.timefilter.timefilter.getTime(),
filters: data.query.filterManager.getFilters(),
@@ -86,5 +71,67 @@ export const getVisualizationInstance = async (
}).get(vis.data.savedSearchId);
}
- return { vis, savedVis, savedSearch, embeddableHandler };
+ return { savedSearch, embeddableHandler };
+};
+
+export const getVisualizationInstanceFromInput = async (
+ visualizeServices: VisualizeServices,
+ input: VisualizeInput
+) => {
+ const { visualizations } = visualizeServices;
+ const visState = input.savedVis as SerializedVis;
+ let vis = await visualizations.createVis(visState.type, cloneDeep(visState));
+ if (vis.type.setup) {
+ try {
+ vis = await vis.type.setup(vis);
+ } catch {
+ // skip this catch block
+ }
+ }
+ const { embeddableHandler, savedSearch } = await createVisualizeEmbeddableAndLinkSavedSearch(
+ vis,
+ visualizeServices
+ );
+ return {
+ vis,
+ embeddableHandler,
+ savedSearch,
+ };
+};
+
+export const getVisualizationInstance = async (
+ visualizeServices: VisualizeServices,
+ /**
+ * opts can be either a saved visualization id passed as string,
+ * or an object of new visualization params.
+ * Both come from url search query
+ */
+ opts?: Record | string
+) => {
+ const { visualizations, savedVisualizations } = visualizeServices;
+ const savedVis: VisSavedObject = await savedVisualizations.get(opts);
+
+ if (typeof opts !== 'string') {
+ savedVis.searchSourceFields = { index: opts?.indexPattern } as SearchSourceFields;
+ }
+ const serializedVis = visualizations.convertToSerializedVis(savedVis);
+ let vis = await visualizations.createVis(serializedVis.type, serializedVis);
+ if (vis.type.setup) {
+ try {
+ vis = await vis.type.setup(vis);
+ } catch {
+ // skip this catch block
+ }
+ }
+
+ const { embeddableHandler, savedSearch } = await createVisualizeEmbeddableAndLinkSavedSearch(
+ vis,
+ visualizeServices
+ );
+ return {
+ vis,
+ embeddableHandler,
+ savedSearch,
+ savedVis,
+ };
};
diff --git a/src/plugins/visualize/public/application/utils/use/index.ts b/src/plugins/visualize/public/application/utils/use/index.ts
index 8bd9456b10572..98d1f11d81a8e 100644
--- a/src/plugins/visualize/public/application/utils/use/index.ts
+++ b/src/plugins/visualize/public/application/utils/use/index.ts
@@ -22,3 +22,4 @@ export { useEditorUpdates } from './use_editor_updates';
export { useSavedVisInstance } from './use_saved_vis_instance';
export { useVisualizeAppState } from './use_visualize_app_state';
export { useLinkedSearchUpdates } from './use_linked_search_updates';
+export { useVisByValue } from './use_vis_byvalue';
diff --git a/src/plugins/visualize/public/application/utils/use/use_editor_updates.ts b/src/plugins/visualize/public/application/utils/use/use_editor_updates.ts
index 0f4b2d34e8e87..c29f6337a6246 100644
--- a/src/plugins/visualize/public/application/utils/use/use_editor_updates.ts
+++ b/src/plugins/visualize/public/application/utils/use/use_editor_updates.ts
@@ -25,8 +25,8 @@ import {
VisualizeServices,
VisualizeAppState,
VisualizeAppStateContainer,
- SavedVisInstance,
IEditorController,
+ VisualizeEditorVisInstance,
} from '../../types';
export const useEditorUpdates = (
@@ -34,21 +34,22 @@ export const useEditorUpdates = (
eventEmitter: EventEmitter,
setHasUnsavedChanges: (value: boolean) => void,
appState: VisualizeAppStateContainer | null,
- savedVisInstance: SavedVisInstance | undefined,
+ visInstance: VisualizeEditorVisInstance | undefined,
visEditorController: IEditorController | undefined
) => {
const [isEmbeddableRendered, setIsEmbeddableRendered] = useState(false);
const [currentAppState, setCurrentAppState] = useState();
useEffect(() => {
- if (appState && savedVisInstance) {
+ if (appState && visInstance) {
const {
timefilter: { timefilter },
filterManager,
queryString,
state$,
} = services.data.query;
- const { embeddableHandler, savedVis, savedSearch, vis } = savedVisInstance;
+ const { embeddableHandler, savedSearch, vis } = visInstance;
+ const savedVis = 'savedVis' in visInstance ? visInstance.savedVis : undefined;
const initialState = appState.getState();
setCurrentAppState(initialState);
@@ -79,15 +80,18 @@ export const useEditorUpdates = (
});
const handleLinkedSearch = (linked: boolean) => {
- if (linked && !savedVis.savedSearchId && savedSearch) {
+ if (linked && savedVis && !savedVis.savedSearchId && savedSearch) {
savedVis.savedSearchId = savedSearch.id;
vis.data.savedSearchId = savedSearch.id;
if (vis.data.searchSource) {
vis.data.searchSource.setParent(savedSearch.searchSource);
}
- } else if (!linked && savedVis.savedSearchId) {
+ } else if (!linked && savedVis && savedVis.savedSearchId) {
delete savedVis.savedSearchId;
delete vis.data.savedSearchId;
+ } else if (!linked && !savedVis) {
+ // delete link when it's not a saved vis
+ delete vis.data.savedSearchId;
}
};
@@ -105,8 +109,7 @@ export const useEditorUpdates = (
const unsubscribeStateUpdates = appState.subscribe((state) => {
setCurrentAppState(state);
-
- if (savedVis.id && !services.history.location.pathname.includes(savedVis.id)) {
+ if (savedVis && savedVis.id && !services.history.location.pathname.includes(savedVis.id)) {
// this filters out the case when manipulating the browser history back/forward
// and initializing different visualizations
return;
@@ -118,6 +121,7 @@ export const useEditorUpdates = (
// if the browser history was changed manually we need to reflect changes in the editor
if (
+ savedVis &&
!isEqual(
{
...services.visualizations.convertFromSerializedVis(vis.serialize()).visState,
@@ -160,14 +164,7 @@ export const useEditorUpdates = (
unsubscribeStateUpdates();
};
}
- }, [
- appState,
- eventEmitter,
- savedVisInstance,
- services,
- setHasUnsavedChanges,
- visEditorController,
- ]);
+ }, [appState, eventEmitter, visInstance, services, setHasUnsavedChanges, visEditorController]);
return { isEmbeddableRendered, currentAppState };
};
diff --git a/src/plugins/visualize/public/application/utils/use/use_linked_search_updates.ts b/src/plugins/visualize/public/application/utils/use/use_linked_search_updates.ts
index e257b72ee751b..7bc38ba6e2842 100644
--- a/src/plugins/visualize/public/application/utils/use/use_linked_search_updates.ts
+++ b/src/plugins/visualize/public/application/utils/use/use_linked_search_updates.ts
@@ -22,24 +22,23 @@ import { i18n } from '@kbn/i18n';
import { EventEmitter } from 'events';
import { Filter } from 'src/plugins/data/public';
-import { VisualizeServices, VisualizeAppStateContainer, SavedVisInstance } from '../../types';
+import {
+ VisualizeServices,
+ VisualizeAppStateContainer,
+ VisualizeEditorVisInstance,
+} from '../../types';
export const useLinkedSearchUpdates = (
services: VisualizeServices,
eventEmitter: EventEmitter,
appState: VisualizeAppStateContainer | null,
- savedVisInstance: SavedVisInstance | undefined
+ visInstance: VisualizeEditorVisInstance | undefined
) => {
useEffect(() => {
- if (
- appState &&
- savedVisInstance &&
- savedVisInstance.savedSearch &&
- savedVisInstance.vis.data.searchSource
- ) {
- const { savedSearch } = savedVisInstance;
+ if (appState && visInstance && visInstance.savedSearch && visInstance.vis.data.searchSource) {
+ const { savedSearch } = visInstance;
// SearchSource is a promise-based stream of search results that can inherit from other search sources.
- const { searchSource } = savedVisInstance.vis.data;
+ const { searchSource } = visInstance.vis.data;
const unlinkFromSavedSearch = () => {
const searchSourceParent = savedSearch.searchSource;
@@ -70,5 +69,5 @@ export const useLinkedSearchUpdates = (
eventEmitter.off('unlinkFromSavedSearch', unlinkFromSavedSearch);
};
}
- }, [appState, eventEmitter, savedVisInstance, services.toastNotifications]);
+ }, [appState, eventEmitter, visInstance, services.toastNotifications]);
};
diff --git a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts
index 764bcb4a327c0..ec815b8cfcbee 100644
--- a/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts
+++ b/src/plugins/visualize/public/application/utils/use/use_saved_vis_instance.ts
@@ -59,7 +59,6 @@ export const useSavedVisInstance = (
const getSavedVisInstance = async () => {
try {
let savedVisInstance: SavedVisInstance;
-
if (history.location.pathname === '/create') {
const searchParams = parse(history.location.search);
const visTypes = services.visualizations.all();
diff --git a/src/plugins/visualize/public/application/utils/use/use_vis_byvalue.ts b/src/plugins/visualize/public/application/utils/use/use_vis_byvalue.ts
new file mode 100644
index 0000000000000..f2758d0cc01a4
--- /dev/null
+++ b/src/plugins/visualize/public/application/utils/use/use_vis_byvalue.ts
@@ -0,0 +1,95 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EventEmitter } from 'events';
+import { useEffect, useRef, useState } from 'react';
+import { VisualizeInput } from 'src/plugins/visualizations/public';
+import { ByValueVisInstance, IEditorController, VisualizeServices } from '../../types';
+import { getVisualizationInstanceFromInput } from '../get_visualization_instance';
+import { getBreadcrumbsPrefixedWithApp, getEditBreadcrumbs } from '../breadcrumbs';
+import { DefaultEditorController } from '../../../../../vis_default_editor/public';
+
+export const useVisByValue = (
+ services: VisualizeServices,
+ eventEmitter: EventEmitter,
+ isChromeVisible: boolean | undefined,
+ valueInput?: VisualizeInput,
+ originatingApp?: string
+) => {
+ const [state, setState] = useState<{
+ byValueVisInstance?: ByValueVisInstance;
+ visEditorController?: IEditorController;
+ }>({});
+ const visEditorRef = useRef(null);
+ const loaded = useRef(false);
+ useEffect(() => {
+ const { chrome } = services;
+ const getVisInstance = async () => {
+ if (!valueInput || loaded.current) {
+ return;
+ }
+ const byValueVisInstance = await getVisualizationInstanceFromInput(services, valueInput);
+ const { embeddableHandler, vis } = byValueVisInstance;
+ const Editor = vis.type.editor || DefaultEditorController;
+ const visEditorController = new Editor(
+ visEditorRef.current,
+ vis,
+ eventEmitter,
+ embeddableHandler
+ );
+
+ if (chrome && originatingApp) {
+ chrome.setBreadcrumbs(getBreadcrumbsPrefixedWithApp(originatingApp));
+ } else if (chrome) {
+ chrome.setBreadcrumbs(getEditBreadcrumbs());
+ }
+
+ loaded.current = true;
+ setState({
+ byValueVisInstance,
+ visEditorController,
+ });
+ };
+
+ getVisInstance();
+ }, [
+ eventEmitter,
+ isChromeVisible,
+ services,
+ state.byValueVisInstance,
+ state.visEditorController,
+ valueInput,
+ originatingApp,
+ ]);
+
+ useEffect(() => {
+ return () => {
+ if (state.visEditorController) {
+ state.visEditorController.destroy();
+ } else if (state.byValueVisInstance?.embeddableHandler) {
+ state.byValueVisInstance.embeddableHandler.destroy();
+ }
+ };
+ }, [state]);
+
+ return {
+ ...state,
+ visEditorRef,
+ };
+};
diff --git a/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.test.ts b/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.test.ts
index 8bde9a049c492..39a2db12ffad1 100644
--- a/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.test.ts
+++ b/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.test.ts
@@ -90,6 +90,7 @@ describe('useVisualizeAppState', () => {
expect(createVisualizeAppState).toHaveBeenCalledWith({
stateDefaults: visualizeAppStateStub,
kbnUrlStateStorage: undefined,
+ byValue: false,
});
expect(mockServices.data.query.filterManager.setAppFilters).toHaveBeenCalledWith(
visualizeAppStateStub.filters
diff --git a/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx b/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx
index c44f67df3729f..935d4b26c98c9 100644
--- a/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx
+++ b/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx
@@ -26,11 +26,14 @@ import { i18n } from '@kbn/i18n';
import { MarkdownSimple, toMountPoint } from '../../../../../kibana_react/public';
import { migrateLegacyQuery } from '../../../../../kibana_legacy/public';
import { esFilters, connectToQueryState } from '../../../../../data/public';
-import { VisualizeServices, VisualizeAppStateContainer, SavedVisInstance } from '../../types';
+import {
+ VisualizeServices,
+ VisualizeAppStateContainer,
+ VisualizeEditorVisInstance,
+} from '../../types';
import { visStateToEditorState } from '../utils';
import { createVisualizeAppState } from '../create_visualize_app_state';
import { VisualizeConstants } from '../../visualize_constants';
-
/**
* This effect is responsible for instantiating the visualize app state container,
* which is in sync with "_a" url param
@@ -38,7 +41,7 @@ import { VisualizeConstants } from '../../visualize_constants';
export const useVisualizeAppState = (
services: VisualizeServices,
eventEmitter: EventEmitter,
- instance?: SavedVisInstance
+ instance?: VisualizeEditorVisInstance
) => {
const [hasUnappliedChanges, setHasUnappliedChanges] = useState(false);
const [appState, setAppState] = useState(null);
@@ -46,10 +49,11 @@ export const useVisualizeAppState = (
useEffect(() => {
if (instance) {
const stateDefaults = visStateToEditorState(instance, services);
-
+ const byValue = !('savedVis' in instance);
const { stateContainer, stopStateSync } = createVisualizeAppState({
stateDefaults,
kbnUrlStateStorage: services.kbnUrlStateStorage,
+ byValue,
});
const onDirtyStateChange = ({ isDirty }: { isDirty: boolean }) => {
diff --git a/src/plugins/visualize/public/application/utils/utils.ts b/src/plugins/visualize/public/application/utils/utils.ts
index 532d87985a0b6..3d8d443d714a5 100644
--- a/src/plugins/visualize/public/application/utils/utils.ts
+++ b/src/plugins/visualize/public/application/utils/utils.ts
@@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n';
import { ChromeStart, DocLinksStart } from 'kibana/public';
import { Filter } from '../../../../data/public';
-import { VisualizeServices, SavedVisInstance } from '../types';
+import { VisualizeServices, VisualizeEditorVisInstance } from '../types';
export const addHelpMenuToAppChrome = (chrome: ChromeStart, docLinks: DocLinksStart) => {
chrome.setHelpExtension({
@@ -54,15 +54,18 @@ export const getDefaultQuery = ({ data }: VisualizeServices) => {
};
export const visStateToEditorState = (
- { vis, savedVis }: SavedVisInstance,
+ visInstance: VisualizeEditorVisInstance,
services: VisualizeServices
) => {
+ const vis = visInstance.vis;
const savedVisState = services.visualizations.convertFromSerializedVis(vis.serialize());
+ const savedVis = 'savedVis' in visInstance ? visInstance.savedVis : undefined;
return {
- uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : vis.uiState.toJSON(),
+ uiState:
+ savedVis && savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : vis.uiState.toJSON(),
query: vis.data.searchSource?.getOwnField('query') || getDefaultQuery(services),
filters: (vis.data.searchSource?.getOwnField('filter') as Filter[]) || [],
vis: { ...savedVisState.visState, title: vis.title },
- linked: !!savedVis.savedSearchId,
+ linked: savedVis && savedVis.id ? !!savedVis.savedSearchId : !!savedVisState.savedSearchId,
};
};
diff --git a/src/plugins/visualize/public/application/visualize_constants.ts b/src/plugins/visualize/public/application/visualize_constants.ts
index adcf27f17dc25..1950fff2733d4 100644
--- a/src/plugins/visualize/public/application/visualize_constants.ts
+++ b/src/plugins/visualize/public/application/visualize_constants.ts
@@ -25,4 +25,5 @@ export const VisualizeConstants = {
WIZARD_STEP_2_PAGE_PATH: '/new/configure',
CREATE_PATH: '/create',
EDIT_PATH: '/edit',
+ EDIT_BY_VALUE_PATH: '/edit_by_value',
};
diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts
index 3299319e613a0..8794593d6c958 100644
--- a/src/plugins/visualize/public/plugin.ts
+++ b/src/plugins/visualize/public/plugin.ts
@@ -39,7 +39,7 @@ import {
} from '../../kibana_utils/public';
import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public';
import { NavigationPublicPluginStart as NavigationStart } from '../../navigation/public';
-import { SharePluginStart } from '../../share/public';
+import { SharePluginStart, SharePluginSetup } from '../../share/public';
import { KibanaLegacySetup, KibanaLegacyStart } from '../../kibana_legacy/public';
import { VisualizationsStart } from '../../visualizations/public';
import { VisualizeConstants } from './application/visualize_constants';
@@ -48,6 +48,16 @@ import { VisualizeServices } from './application/types';
import { DEFAULT_APP_CATEGORIES } from '../../../core/public';
import { SavedObjectsStart } from '../../saved_objects/public';
import { EmbeddableStart } from '../../embeddable/public';
+import { UiActionsStart, VISUALIZE_FIELD_TRIGGER } from '../../ui_actions/public';
+import {
+ setUISettings,
+ setApplication,
+ setIndexPatterns,
+ setQueryService,
+ setShareService,
+} from './services';
+import { visualizeFieldAction } from './actions/visualize_field_action';
+import { createVisualizeUrlGenerator } from './url_generator';
export interface VisualizePluginStartDependencies {
data: DataPublicPluginStart;
@@ -57,12 +67,14 @@ export interface VisualizePluginStartDependencies {
embeddable: EmbeddableStart;
kibanaLegacy: KibanaLegacyStart;
savedObjects: SavedObjectsStart;
+ uiActions: UiActionsStart;
}
export interface VisualizePluginSetupDependencies {
home?: HomePublicPluginSetup;
kibanaLegacy: KibanaLegacySetup;
data: DataPublicPluginSetup;
+ share?: SharePluginSetup;
}
export interface FeatureFlagConfig {
@@ -80,7 +92,7 @@ export class VisualizePlugin
public async setup(
core: CoreSetup,
- { home, kibanaLegacy, data }: VisualizePluginSetupDependencies
+ { home, kibanaLegacy, data, share }: VisualizePluginSetupDependencies
) {
const {
appMounted,
@@ -113,6 +125,18 @@ export class VisualizePlugin
this.stopUrlTracking = () => {
stopUrlTracker();
};
+ if (share) {
+ share.urlGenerators.registerUrlGenerator(
+ createVisualizeUrlGenerator(async () => {
+ const [coreStart] = await core.getStartServices();
+ return {
+ appBasePath: coreStart.application.getUrlForApp('visualize'),
+ useHashedUrl: coreStart.uiSettings.get('state:storeInSessionStorage'),
+ };
+ })
+ );
+ }
+ setUISettings(core.uiSettings);
core.application.register({
id: 'visualize',
@@ -140,7 +164,6 @@ export class VisualizePlugin
const unlistenParentHistory = params.history.listen(() => {
window.dispatchEvent(new HashChangeEvent('hashchange'));
});
-
/**
* current implementation uses 2 history objects:
* 1. the hash history (used for the react hash router)
@@ -207,7 +230,15 @@ export class VisualizePlugin
}
}
- public start(core: CoreStart, plugins: VisualizePluginStartDependencies) {}
+ public start(core: CoreStart, plugins: VisualizePluginStartDependencies) {
+ setApplication(core.application);
+ setIndexPatterns(plugins.data.indexPatterns);
+ setQueryService(plugins.data.query);
+ if (plugins.share) {
+ setShareService(plugins.share);
+ }
+ plugins.uiActions.addTriggerAction(VISUALIZE_FIELD_TRIGGER, visualizeFieldAction);
+ }
stop() {
if (this.stopUrlTracking) {
diff --git a/src/plugins/visualize/public/services.ts b/src/plugins/visualize/public/services.ts
new file mode 100644
index 0000000000000..8190872ec6508
--- /dev/null
+++ b/src/plugins/visualize/public/services.ts
@@ -0,0 +1,37 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ApplicationStart, IUiSettingsClient } from '../../../core/public';
+import { createGetterSetter } from '../../../plugins/kibana_utils/public';
+import { IndexPatternsContract, DataPublicPluginStart } from '../../../plugins/data/public';
+import { SharePluginStart } from '../../../plugins/share/public';
+
+export const [getUISettings, setUISettings] = createGetterSetter('UISettings');
+
+export const [getApplication, setApplication] = createGetterSetter('Application');
+
+export const [getShareService, setShareService] = createGetterSetter('Share');
+
+export const [getIndexPatterns, setIndexPatterns] = createGetterSetter(
+ 'IndexPatterns'
+);
+
+export const [getQueryService, setQueryService] = createGetterSetter<
+ DataPublicPluginStart['query']
+>('Query');
diff --git a/src/plugins/visualize/public/url_generator.test.ts b/src/plugins/visualize/public/url_generator.test.ts
new file mode 100644
index 0000000000000..8c8a0f70c15e3
--- /dev/null
+++ b/src/plugins/visualize/public/url_generator.test.ts
@@ -0,0 +1,100 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { createVisualizeUrlGenerator } from './url_generator';
+import { esFilters } from '../../data/public';
+
+const APP_BASE_PATH: string = 'test/app/visualize';
+const VISUALIZE_ID: string = '13823000-99b9-11ea-9eb6-d9e8adceb647';
+const INDEXPATTERN_ID: string = '13823000-99b9-11ea-9eb6-d9e8adceb647';
+
+describe('visualize url generator', () => {
+ test('creates a link to a new visualization', async () => {
+ const generator = createVisualizeUrlGenerator(() =>
+ Promise.resolve({
+ appBasePath: APP_BASE_PATH,
+ useHashedUrl: false,
+ })
+ );
+ const url = await generator.createUrl!({ indexPatternId: INDEXPATTERN_ID, type: 'table' });
+ expect(url).toMatchInlineSnapshot(
+ `"test/app/visualize#/create?_g=()&_a=()&indexPattern=${INDEXPATTERN_ID}&type=table"`
+ );
+ });
+
+ test('creates a link with global time range set up', async () => {
+ const generator = createVisualizeUrlGenerator(() =>
+ Promise.resolve({
+ appBasePath: APP_BASE_PATH,
+ useHashedUrl: false,
+ })
+ );
+ const url = await generator.createUrl!({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ indexPatternId: INDEXPATTERN_ID,
+ type: 'table',
+ });
+ expect(url).toMatchInlineSnapshot(
+ `"test/app/visualize#/create?_g=(time:(from:now-15m,mode:relative,to:now))&_a=()&indexPattern=${INDEXPATTERN_ID}&type=table"`
+ );
+ });
+
+ test('creates a link with filters, time range, refresh interval and query to a saved visualization', async () => {
+ const generator = createVisualizeUrlGenerator(() =>
+ Promise.resolve({
+ appBasePath: APP_BASE_PATH,
+ useHashedUrl: false,
+ indexPatternId: INDEXPATTERN_ID,
+ type: 'table',
+ })
+ );
+ const url = await generator.createUrl!({
+ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
+ refreshInterval: { pause: false, value: 300 },
+ visualizationId: VISUALIZE_ID,
+ filters: [
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'q1' },
+ },
+ {
+ meta: {
+ alias: null,
+ disabled: false,
+ negate: false,
+ },
+ query: { query: 'q1' },
+ $state: {
+ store: esFilters.FilterStateStore.GLOBAL_STATE,
+ },
+ },
+ ],
+ query: { query: 'q2', language: 'kuery' },
+ indexPatternId: INDEXPATTERN_ID,
+ type: 'table',
+ });
+ expect(url).toMatchInlineSnapshot(
+ `"test/app/visualize#/edit/${VISUALIZE_ID}?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),query:(language:kuery,query:q2))&indexPattern=${INDEXPATTERN_ID}&type=table"`
+ );
+ });
+});
diff --git a/src/plugins/visualize/public/url_generator.ts b/src/plugins/visualize/public/url_generator.ts
new file mode 100644
index 0000000000000..38b7633c6fde1
--- /dev/null
+++ b/src/plugins/visualize/public/url_generator.ts
@@ -0,0 +1,137 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ TimeRange,
+ Filter,
+ Query,
+ esFilters,
+ QueryState,
+ RefreshInterval,
+} from '../../data/public';
+import { setStateToKbnUrl } from '../../kibana_utils/public';
+import { UrlGeneratorsDefinition } from '../../share/public';
+
+const STATE_STORAGE_KEY = '_a';
+const GLOBAL_STATE_STORAGE_KEY = '_g';
+
+export const VISUALIZE_APP_URL_GENERATOR = 'VISUALIZE_APP_URL_GENERATOR';
+
+export interface VisualizeUrlGeneratorState {
+ /**
+ * If given, it will load the given visualization else will load the create a new visualization page.
+ */
+ visualizationId?: string;
+ /**
+ * Optionally set the time range in the time picker.
+ */
+ timeRange?: TimeRange;
+
+ /**
+ * Optional set indexPatternId.
+ */
+ indexPatternId?: string;
+
+ /**
+ * Optional set visualization type.
+ */
+ type?: string;
+
+ /**
+ * Optionally set the visualization.
+ */
+ vis?: unknown;
+
+ /**
+ * Optionally set the refresh interval.
+ */
+ refreshInterval?: RefreshInterval;
+
+ /**
+ * 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.
+ */
+ hash?: boolean;
+}
+
+export const createVisualizeUrlGenerator = (
+ getStartServices: () => Promise<{
+ appBasePath: string;
+ useHashedUrl: boolean;
+ }>
+): UrlGeneratorsDefinition => ({
+ id: VISUALIZE_APP_URL_GENERATOR,
+ createUrl: async ({
+ visualizationId,
+ filters,
+ indexPatternId,
+ query,
+ refreshInterval,
+ vis,
+ type,
+ timeRange,
+ hash,
+ }: VisualizeUrlGeneratorState): Promise => {
+ const startServices = await getStartServices();
+ const useHash = hash ?? startServices.useHashedUrl;
+ const appBasePath = startServices.appBasePath;
+ const mode = visualizationId ? `edit/${visualizationId}` : `create`;
+
+ const appState: {
+ query?: Query;
+ filters?: Filter[];
+ vis?: unknown;
+ } = {};
+ const queryState: QueryState = {};
+
+ if (query) appState.query = query;
+ if (filters && filters.length)
+ appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f));
+ if (vis) appState.vis = vis;
+
+ if (timeRange) queryState.time = timeRange;
+ if (filters && filters.length)
+ queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f));
+ if (refreshInterval) queryState.refreshInterval = refreshInterval;
+
+ let url = `${appBasePath}#/${mode}`;
+ url = setStateToKbnUrl(GLOBAL_STATE_STORAGE_KEY, queryState, { useHash }, url);
+ url = setStateToKbnUrl(STATE_STORAGE_KEY, appState, { useHash }, url);
+
+ if (indexPatternId) {
+ url = `${url}&indexPattern=${indexPatternId}`;
+ }
+
+ if (type) {
+ url = `${url}&type=${type}`;
+ }
+
+ return url;
+ },
+});
diff --git a/test/functional/apps/context/_context_navigation.js b/test/functional/apps/context/_context_navigation.js
index babefe488d7bc..c5af2fcb79296 100644
--- a/test/functional/apps/context/_context_navigation.js
+++ b/test/functional/apps/context/_context_navigation.js
@@ -18,11 +18,11 @@
*/
const TEST_FILTER_COLUMN_NAMES = [
- ['extension', 'jpg'],
[
'agent',
'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24',
],
+ ['extension', 'jpg'],
];
export default function ({ getService, getPageObjects }) {
diff --git a/test/functional/apps/dashboard/dashboard_filter_bar.js b/test/functional/apps/dashboard/dashboard_filter_bar.js
index 273779a42d3f9..cbef274b78e93 100644
--- a/test/functional/apps/dashboard/dashboard_filter_bar.js
+++ b/test/functional/apps/dashboard/dashboard_filter_bar.js
@@ -67,7 +67,7 @@ export default function ({ getService, getPageObjects }) {
it('uses default index pattern on an empty dashboard', async () => {
await testSubjects.click('addFilter');
- await dashboardExpect.fieldSuggestions(['bytes']);
+ await dashboardExpect.fieldSuggestions(['agent']);
await filterBar.ensureFieldEditorModalIsClosed();
});
diff --git a/test/functional/apps/discover/_discover_histogram.ts b/test/functional/apps/discover/_discover_histogram.ts
index 5c78bfccbb966..e06783174e83b 100644
--- a/test/functional/apps/discover/_discover_histogram.ts
+++ b/test/functional/apps/discover/_discover_histogram.ts
@@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const elasticChart = getService('elasticChart');
const kibanaServer = getService('kibanaServer');
+ const security = getService('security');
const PageObjects = getPageObjects(['settings', 'common', 'discover', 'header', 'timePicker']);
const defaultSettings = {
defaultIndex: 'long-window-logstash-*',
@@ -35,12 +36,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await esArchiver.loadIfNeeded('logstash_functional');
await esArchiver.load('long_window_logstash');
await esArchiver.load('long_window_logstash_index_pattern');
+ await security.testUser.setRoles(['kibana_admin', 'long_window_logstash']);
await kibanaServer.uiSettings.replace(defaultSettings);
await PageObjects.common.navigateToApp('discover');
});
after(async () => {
await esArchiver.unload('long_window_logstash');
await esArchiver.unload('long_window_logstash_index_pattern');
+ await security.testUser.restoreDefaults();
});
async function prepareTest(fromTime: string, toTime: string, interval: string) {
diff --git a/test/functional/apps/discover/_field_visualize.ts b/test/functional/apps/discover/_field_visualize.ts
index b0db6c149e41a..c95211e98cdba 100644
--- a/test/functional/apps/discover/_field_visualize.ts
+++ b/test/functional/apps/discover/_field_visualize.ts
@@ -87,6 +87,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await inspector.close();
});
+ it('should not show the "Visualize" button for geo field', async () => {
+ await PageObjects.discover.findFieldByName('geo.coordinates');
+ log.debug('visualize a geo field');
+ await PageObjects.discover.expectMissingFieldListItemVisualize('geo.coordinates');
+ });
+
it('should preserve app filters in visualize', async () => {
await filterBar.addFilter('bytes', 'is between', '3500', '4000');
await PageObjects.discover.findFieldByName('geo.src');
diff --git a/test/functional/apps/management/_import_objects.js b/test/functional/apps/management/_import_objects.js
index 03db3a2b108f2..5fbeb978f9a1c 100644
--- a/test/functional/apps/management/_import_objects.js
+++ b/test/functional/apps/management/_import_objects.js
@@ -49,12 +49,13 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.savedObjects.checkImportSucceeded();
await PageObjects.savedObjects.clickImportDone();
- // get all the elements in the table, and index them by the 'title' visible text field
- const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
log.debug("check that 'Log Agents' is in table as a visualization");
- expect(elements['Log Agents'].objectType).to.eql('visualization');
+ expect(await PageObjects.savedObjects.getObjectTypeByTitle('Log Agents')).to.eql(
+ 'visualization'
+ );
+
+ await PageObjects.savedObjects.clickRelationshipsByTitle('logstash-*');
- await elements['logstash-*'].relationshipsElement.click();
const flyout = keyBy(await PageObjects.savedObjects.getRelationshipFlyout(), 'title');
log.debug(
"check that 'Shared-Item Visualization AreaChart' shows 'logstash-*' as it's Parent"
@@ -150,8 +151,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should not import saved objects linked to saved searches when saved search index pattern does not exist', async function () {
- const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
- await elements['logstash-*'].checkbox.click();
+ await PageObjects.savedObjects.clickCheckboxByTitle('logstash-*');
await PageObjects.savedObjects.clickDelete();
await PageObjects.savedObjects.importFile(
@@ -182,8 +182,7 @@ export default function ({ getService, getPageObjects }) {
it('should import saved objects with index patterns when index patterns does not exists', async () => {
// First, we need to delete the index pattern
- const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
- await elements['logstash-*'].checkbox.click();
+ await PageObjects.savedObjects.clickCheckboxByTitle('logstash-*');
await PageObjects.savedObjects.clickDelete();
// Then, import the objects
@@ -321,8 +320,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.savedObjects.clickImportDone();
// Second, we need to delete the index pattern
- const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
- await elements['logstash-*'].checkbox.click();
+ await PageObjects.savedObjects.clickCheckboxByTitle('logstash-*');
await PageObjects.savedObjects.clickDelete();
// Last, import a saved object connected to the saved search
@@ -353,8 +351,7 @@ export default function ({ getService, getPageObjects }) {
it('should import saved objects with index patterns when index patterns does not exists', async () => {
// First, we need to delete the index pattern
- const elements = keyBy(await PageObjects.savedObjects.getElementsInTable(), 'title');
- await elements['logstash-*'].checkbox.click();
+ await PageObjects.savedObjects.clickCheckboxByTitle('logstash-*');
await PageObjects.savedObjects.clickDelete();
// Then, import the objects
diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts
index 18d6e93090e8b..74d5798d127c3 100644
--- a/test/functional/apps/visualize/_tsvb_chart.ts
+++ b/test/functional/apps/visualize/_tsvb_chart.ts
@@ -47,7 +47,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visualBuilder.checkVisualBuilderIsPresent();
});
- describe('metric', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/75127
+ describe.skip('metric', () => {
beforeEach(async () => {
await PageObjects.visualBuilder.resetPage();
await PageObjects.visualBuilder.clickMetric();
diff --git a/test/functional/apps/visualize/input_control_vis/chained_controls.js b/test/functional/apps/visualize/input_control_vis/chained_controls.js
index 89cca7dc7827e..035245b50d436 100644
--- a/test/functional/apps/visualize/input_control_vis/chained_controls.js
+++ b/test/functional/apps/visualize/input_control_vis/chained_controls.js
@@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }) {
const find = getService('find');
const comboBox = getService('comboBox');
- describe('chained controls', function () {
+ // FLAKY: https://github.com/elastic/kibana/issues/68472
+ describe.skip('chained controls', function () {
this.tags('includeFirefox');
before(async () => {
@@ -39,7 +40,7 @@ export default function ({ getService, getPageObjects }) {
it('should disable child control when parent control is not set', async () => {
const parentControlMenu = await comboBox.getOptionsList('listControlSelect0');
- expect(parentControlMenu.trim().split('\n').join()).to.equal('BD,BR,CN,ID,IN,JP,NG,PK,RU,US');
+ expect(parentControlMenu.trim().split('\n').join()).to.equal('BD,BR,CN,ID,IN,JP,NG,PK,RU');
const childControlInput = await find.byCssSelector('[data-test-subj="inputControl1"] input');
const isDisabled = await childControlInput.getAttribute('disabled');
diff --git a/test/functional/apps/visualize/input_control_vis/dynamic_options.js b/test/functional/apps/visualize/input_control_vis/dynamic_options.js
index b19f64511a23b..400be471be4b6 100644
--- a/test/functional/apps/visualize/input_control_vis/dynamic_options.js
+++ b/test/functional/apps/visualize/input_control_vis/dynamic_options.js
@@ -34,13 +34,13 @@ export default function ({ getService, getPageObjects }) {
it('should fetch new options when string field is filtered', async () => {
const initialOptions = await comboBox.getOptionsList('listControlSelect0');
- expect(initialOptions.trim().split('\n').join()).to.equal('BD,BR,CN,ID,IN,JP,NG,PK,RU,US');
+ expect(initialOptions.trim().split('\n').join()).to.equal('BD,BR,CN,ID,IN,JP,NG,PK,RU');
await comboBox.filterOptionsList('listControlSelect0', 'R');
await PageObjects.header.waitUntilLoadingHasFinished();
const updatedOptions = await comboBox.getOptionsList('listControlSelect0');
- expect(updatedOptions.trim().split('\n').join()).to.equal('AR,BR,FR,GR,IR,KR,RO,RU,RW,TR');
+ expect(updatedOptions.trim().split('\n').join()).to.equal('AR,BR,FR,GR,IR,KR,RO,RU,RW');
});
it('should not fetch new options when non-string is filtered', async () => {
@@ -74,13 +74,13 @@ export default function ({ getService, getPageObjects }) {
it('should fetch new options when string field is filtered', async () => {
const initialOptions = await comboBox.getOptionsList('listControlSelect1');
- expect(initialOptions.trim().split('\n').join()).to.equal('BD,BR,CN,ID,IN,JP,MX,NG,PK,US');
+ expect(initialOptions.trim().split('\n').join()).to.equal('BD,BR,CN,ID,IN,JP,MX,NG,PK');
await comboBox.filterOptionsList('listControlSelect1', 'R');
await PageObjects.header.waitUntilLoadingHasFinished();
const updatedOptions = await comboBox.getOptionsList('listControlSelect1');
- expect(updatedOptions.trim().split('\n').join()).to.equal('AR,BR,FR,GR,IR,KR,RO,RS,RU,TR');
+ expect(updatedOptions.trim().split('\n').join()).to.equal('AR,BR,FR,GR,IR,KR,RO,RS,RU');
});
});
});
diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts
index c558d9e2d8a31..5a224d930ee42 100644
--- a/test/functional/page_objects/discover_page.ts
+++ b/test/functional/page_objects/discover_page.ts
@@ -332,6 +332,7 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider
public async selectIndexPattern(indexPattern: string) {
await testSubjects.click('indexPattern-switch-link');
+ await find.setValue('[data-test-subj="indexPattern-switcher"] input', indexPattern);
await find.clickByCssSelector(
`[data-test-subj="indexPattern-switcher"] [title="${indexPattern}"]`
);
diff --git a/test/functional/page_objects/management/saved_objects_page.ts b/test/functional/page_objects/management/saved_objects_page.ts
index 03d21aa4aa52f..ad82ea9b6fbc1 100644
--- a/test/functional/page_objects/management/saved_objects_page.ts
+++ b/test/functional/page_objects/management/saved_objects_page.ts
@@ -17,6 +17,7 @@
* under the License.
*/
+import { keyBy } from 'lodash';
import { map as mapAsync } from 'bluebird';
import { FtrProviderContext } from '../../ftr_provider_context';
@@ -34,6 +35,8 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv
await searchBox.clearValue();
await searchBox.type(objectName);
await searchBox.pressKeys(browser.keys.ENTER);
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await this.waitTableIsLoaded();
}
async importFile(path: string, overwriteAll = true) {
@@ -99,6 +102,56 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv
});
}
+ async clickRelationshipsByTitle(title: string) {
+ const table = keyBy(await this.getElementsInTable(), 'title');
+ // should we check if table size > 0 and log error if not?
+ if (table[title].menuElement) {
+ log.debug(`we found a context menu element for (${title}) so click it`);
+ await table[title].menuElement?.click();
+ // Wait for context menu to render
+ const menuPanel = await find.byCssSelector('.euiContextMenuPanel');
+ await (await menuPanel.findByTestSubject('savedObjectsTableAction-relationships')).click();
+ } else {
+ log.debug(
+ `we didn't find a menu element so should be a relastionships element for (${title}) to click`
+ );
+ // or the action elements are on the row without the menu
+ await table[title].relationshipsElement?.click();
+ }
+ }
+
+ async clickCopyToSpaceByTitle(title: string) {
+ const table = keyBy(await this.getElementsInTable(), 'title');
+ // should we check if table size > 0 and log error if not?
+ if (table[title].menuElement) {
+ log.debug(`we found a context menu element for (${title}) so click it`);
+ await table[title].menuElement?.click();
+ // Wait for context menu to render
+ const menuPanel = await find.byCssSelector('.euiContextMenuPanel');
+ await (
+ await menuPanel.findByTestSubject('savedObjectsTableAction-copy_saved_objects_to_space')
+ ).click();
+ } else {
+ log.debug(
+ `we didn't find a menu element so should be a "copy to space" element for (${title}) to click`
+ );
+ // or the action elements are on the row without the menu
+ await table[title].copySaveObjectsElement?.click();
+ }
+ }
+
+ async clickCheckboxByTitle(title: string) {
+ const table = keyBy(await this.getElementsInTable(), 'title');
+ // should we check if table size > 0 and log error if not?
+ await table[title].checkbox.click();
+ }
+
+ async getObjectTypeByTitle(title: string) {
+ const table = keyBy(await this.getElementsInTable(), 'title');
+ // should we check if table size > 0 and log error if not?
+ return table[title].objectType;
+ }
+
async getElementsInTable() {
const rows = await testSubjects.findAll('~savedObjectsTableRow');
return mapAsync(rows, async (row) => {
@@ -107,23 +160,45 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv
const objectType = await row.findByTestSubject('objectType');
const titleElement = await row.findByTestSubject('savedObjectsTableRowTitle');
// not all rows have inspect button - Advanced Settings objects don't
- let inspectElement;
- const innerHtml = await row.getAttribute('innerHTML');
- if (innerHtml.includes('Inspect')) {
+ // Advanced Settings has 2 actions,
+ // data-test-subj="savedObjectsTableAction-relationships"
+ // data-test-subj="savedObjectsTableAction-copy_saved_objects_to_space"
+ // Some other objects have the ...
+ // data-test-subj="euiCollapsedItemActionsButton"
+ // Maybe some objects still have the inspect element visible?
+ // !!! Also note that since we don't have spaces on OSS, the actions for the same object can be different depending on OSS or not
+ let menuElement = null;
+ let inspectElement = null;
+ let relationshipsElement = null;
+ let copySaveObjectsElement = null;
+ const actions = await row.findByClassName('euiTableRowCell--hasActions');
+ // getting the innerHTML and checking if it 'includes' a string is faster than a timeout looking for each element
+ const actionsHTML = await actions.getAttribute('innerHTML');
+ if (actionsHTML.includes('euiCollapsedItemActionsButton')) {
+ menuElement = await row.findByTestSubject('euiCollapsedItemActionsButton');
+ }
+ if (actionsHTML.includes('savedObjectsTableAction-inspect')) {
inspectElement = await row.findByTestSubject('savedObjectsTableAction-inspect');
- } else {
- inspectElement = null;
}
- const relationshipsElement = await row.findByTestSubject(
- 'savedObjectsTableAction-relationships'
- );
+ if (actionsHTML.includes('savedObjectsTableAction-relationships')) {
+ relationshipsElement = await row.findByTestSubject(
+ 'savedObjectsTableAction-relationships'
+ );
+ }
+ if (actionsHTML.includes('savedObjectsTableAction-copy_saved_objects_to_space')) {
+ copySaveObjectsElement = await row.findByTestSubject(
+ 'savedObjectsTableAction-copy_saved_objects_to_space'
+ );
+ }
return {
checkbox,
objectType: await objectType.getAttribute('aria-label'),
titleElement,
title: await titleElement.getVisibleText(),
+ menuElement,
inspectElement,
relationshipsElement,
+ copySaveObjectsElement,
};
});
}
diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts
index fa42eb60fa410..3f9775d7a75f3 100644
--- a/test/functional/services/listing_table.ts
+++ b/test/functional/services/listing_table.ts
@@ -182,7 +182,7 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider
await retry.tryForTime(20000, async () => {
// newItemButton button is only visible when there are items in the listing table is displayed.
const isnNewItemButtonPresent = await testSubjects.exists('newItemButton', {
- timeout: 5000,
+ timeout: 10000,
});
if (isnNewItemButtonPresent) {
await testSubjects.click('newItemButton');
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
index f3e5520a14fe2..b04fbc2bb2136 100644
--- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "26.3.1",
+ "@elastic/eui": "27.4.0",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts
index c611300eade10..9ec82e291e537 100644
--- a/test/plugin_functional/config.ts
+++ b/test/plugin_functional/config.ts
@@ -31,6 +31,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
return {
testFiles: [
+ require.resolve('./test_suites/core'),
require.resolve('./test_suites/custom_visualizations'),
require.resolve('./test_suites/panel_actions'),
require.resolve('./test_suites/core_plugins'),
diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/kibana.json b/test/plugin_functional/plugins/core_plugin_route_timeouts/kibana.json
new file mode 100644
index 0000000000000..6fbddad22b764
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/kibana.json
@@ -0,0 +1,8 @@
+{
+ "id": "core_plugin_route_timeouts",
+ "version": "0.0.1",
+ "kibanaVersion": "kibana",
+ "configPath": ["core_plugin_route_timeouts"],
+ "server": true,
+ "ui": false
+}
diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/package.json b/test/plugin_functional/plugins/core_plugin_route_timeouts/package.json
new file mode 100644
index 0000000000000..a9c520338457b
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "core_plugin_route_timeouts",
+ "version": "1.0.0",
+ "main": "target/test/plugin_functional/plugins/core_plugin_route_timeouts",
+ "kibana": {
+ "version": "kibana",
+ "templateVersion": "1.0.0"
+ },
+ "license": "Apache-2.0",
+ "scripts": {
+ "kbn": "node ../../../../scripts/kbn.js",
+ "build": "rm -rf './target' && tsc"
+ },
+ "devDependencies": {
+ "typescript": "3.9.5"
+ }
+}
diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts
new file mode 100644
index 0000000000000..4fdd6ef0ab04a
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/index.ts
@@ -0,0 +1,23 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { CorePluginRouteTimeoutsPlugin } from './plugin';
+export { PluginARequestContext } from './plugin';
+
+export const plugin = () => new CorePluginRouteTimeoutsPlugin();
diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts
new file mode 100644
index 0000000000000..c5bdf6cce084c
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/server/plugin.ts
@@ -0,0 +1,124 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Plugin, CoreSetup } from 'kibana/server';
+import { schema } from '@kbn/config-schema';
+
+export interface PluginARequestContext {
+ ping: () => Promise;
+}
+
+declare module 'kibana/server' {
+ interface RequestHandlerContext {
+ pluginA?: PluginARequestContext;
+ }
+}
+
+export class CorePluginRouteTimeoutsPlugin implements Plugin {
+ public setup(core: CoreSetup, deps: {}) {
+ const { http } = core;
+
+ const router = http.createRouter();
+
+ router.post(
+ {
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { payload: 100 },
+ },
+ path: '/short_payload_timeout',
+ validate: false,
+ },
+ async (context, req, res) => {
+ return res.ok({});
+ }
+ );
+
+ router.post(
+ {
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { payload: 10000 },
+ },
+ path: '/longer_payload_timeout',
+ validate: false,
+ },
+ async (context, req, res) => {
+ return res.ok({});
+ }
+ );
+
+ router.post(
+ {
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 10 },
+ },
+ path: '/short_idle_socket_timeout',
+ validate: {
+ body: schema.maybe(
+ schema.object({
+ responseDelay: schema.maybe(schema.number()),
+ })
+ ),
+ },
+ },
+ async (context, req, res) => {
+ if (req.body?.responseDelay) {
+ await new Promise((resolve) => setTimeout(resolve, req.body!.responseDelay));
+ }
+ return res.ok({});
+ }
+ );
+
+ router.post(
+ {
+ options: {
+ body: {
+ accepts: ['application/json'],
+ },
+ timeout: { idleSocket: 5000 },
+ },
+ path: '/longer_idle_socket_timeout',
+ validate: {
+ body: schema.maybe(
+ schema.object({
+ responseDelay: schema.maybe(schema.number()),
+ })
+ ),
+ },
+ },
+ async (context, req, res) => {
+ if (req.body?.responseDelay) {
+ await new Promise((resolve) => setTimeout(resolve, req.body!.responseDelay));
+ }
+ return res.ok({});
+ }
+ );
+ }
+
+ public start() {}
+ public stop() {}
+}
diff --git a/test/plugin_functional/plugins/core_plugin_route_timeouts/tsconfig.json b/test/plugin_functional/plugins/core_plugin_route_timeouts/tsconfig.json
new file mode 100644
index 0000000000000..d0751f31ecc5e
--- /dev/null
+++ b/test/plugin_functional/plugins/core_plugin_route_timeouts/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./target",
+ "skipLibCheck": true
+ },
+ "include": [
+ "server/**/*.ts",
+ "../../../../typings/**/*"
+ ],
+ "exclude": []
+}
diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json
index b9c5b3bc5b836..e4a66fe47d8f2 100644
--- a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json
+++ b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "26.3.1",
+ "@elastic/eui": "27.4.0",
"react": "^16.12.0"
},
"scripts": {
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
index 95fafdf221c64..0e560dd7be7c3 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "26.3.1",
+ "@elastic/eui": "27.4.0",
"react": "^16.12.0"
},
"scripts": {
diff --git a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.js b/test/plugin_functional/test_suites/core/index.ts
similarity index 76%
rename from src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.js
rename to test/plugin_functional/test_suites/core/index.ts
index 452e85c6405fe..5852e21fc3b39 100644
--- a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.js
+++ b/test/plugin_functional/test_suites/core/index.ts
@@ -16,15 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { PluginFunctionalProviderContext } from '../../services';
-import moment from 'moment';
-
-const TIME_MODE = 'absolute';
-
-export const createBrushHandler = (timefilter) => (from, to) => {
- timefilter.setTime({
- from: moment(from).toISOString(),
- to: moment(to).toISOString(),
- mode: TIME_MODE,
+export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
+ describe('core', function () {
+ loadTestFile(require.resolve('./route'));
});
-};
+}
diff --git a/test/plugin_functional/test_suites/core/route.ts b/test/plugin_functional/test_suites/core/route.ts
new file mode 100644
index 0000000000000..becde49a69714
--- /dev/null
+++ b/test/plugin_functional/test_suites/core/route.ts
@@ -0,0 +1,174 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import expect from '@kbn/expect';
+import { Test } from 'supertest';
+import { PluginFunctionalProviderContext } from '../../services';
+
+export default function ({ getService }: PluginFunctionalProviderContext) {
+ const supertest = getService('supertest');
+
+ describe('route', function () {
+ describe('timeouts', function () {
+ const writeBodyCharAtATime = (request: Test, body: string, interval: number) => {
+ return new Promise((resolve, reject) => {
+ let i = 0;
+ const intervalId = setInterval(() => {
+ if (i < body.length) {
+ request.write(body[i++]);
+ } else {
+ clearInterval(intervalId);
+ request.end((err, res) => {
+ resolve(res);
+ });
+ }
+ }, interval);
+ request.on('error', (err) => {
+ clearInterval(intervalId);
+ reject(err);
+ });
+ });
+ };
+
+ describe('payload', function () {
+ it(`should timeout if POST payload sending is too slow`, async () => {
+ // start the request
+ const request = supertest
+ .post('/short_payload_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"foo":"bar"}', 10);
+
+ await result.then(
+ (res) => {
+ expect(res).to.be(undefined);
+ },
+ (err) => {
+ expect(err.message).to.be('Request Timeout');
+ }
+ );
+ });
+
+ it(`should not timeout if POST payload sending is quick`, async () => {
+ // start the request
+ const request = supertest
+ .post('/longer_payload_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"foo":"bar"}', 10);
+
+ await result.then(
+ (res) => {
+ expect(res).to.have.property('statusCode', 200);
+ },
+ (err) => {
+ expect(err).to.be(undefined);
+ }
+ );
+ });
+ });
+
+ describe('idle socket', function () {
+ it('should timeout if payload sending has too long of an idle period', async function () {
+ // start the request
+ const request = supertest
+ .post('/short_idle_socket_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"responseDelay":100}', 20);
+
+ await result.then(
+ (res) => {
+ expect(res).to.be(undefined);
+ },
+ (err) => {
+ expect(err.message).to.be('socket hang up');
+ }
+ );
+ });
+
+ it('should not timeout if payload sending does not have too long of an idle period', async function () {
+ // start the request
+ const request = supertest
+ .post('/longer_idle_socket_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"responseDelay":0}', 10);
+
+ await result.then(
+ (res) => {
+ expect(res).to.have.property('statusCode', 200);
+ },
+ (err) => {
+ expect(err).to.be(undefined);
+ }
+ );
+ });
+
+ it('should timeout if servers response is too slow', async function () {
+ // start the request
+ const request = supertest
+ .post('/short_idle_socket_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"responseDelay":100}', 0);
+
+ await result.then(
+ (res) => {
+ expect(res).to.be(undefined);
+ },
+ (err) => {
+ expect(err.message).to.be('socket hang up');
+ }
+ );
+ });
+
+ it('should not timeout if servers response is fast enough', async function () {
+ // start the request
+ const request = supertest
+ .post('/longer_idle_socket_timeout')
+ .set('Content-Type', 'application/json')
+ .set('Transfer-Encoding', 'chunked')
+ .set('kbn-xsrf', 'true');
+
+ const result = writeBodyCharAtATime(request, '{"responseDelay":100}', 0);
+
+ await result.then(
+ (res) => {
+ expect(res).to.have.property('statusCode', 200);
+ },
+ (err) => {
+ expect(err).to.be(undefined);
+ }
+ );
+ });
+ });
+ });
+ });
+}
diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy
index 173c5b7e11764..00668f2ccdaa7 100644
--- a/vars/kibanaPipeline.groovy
+++ b/vars/kibanaPipeline.groovy
@@ -170,7 +170,6 @@ def uploadCoverageArtifacts(prefix, pattern) {
def withGcsArtifactUpload(workerName, closure) {
def uploadPrefix = "kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}"
def ARTIFACT_PATTERNS = [
- '**/target/public/.kbn-optimizer-cache',
'target/kibana-*',
'target/test-metrics/*',
'target/kibana-security-solution/**/*.png',
@@ -221,7 +220,7 @@ def publishJunit() {
}
}
-def sendMail() {
+def sendMail(Map params = [:]) {
// If the build doesn't have a result set by this point, there haven't been any errors and it can be marked as a success
// The e-mail plugin for the infra e-mail depends upon this being set
currentBuild.result = currentBuild.result ?: 'SUCCESS'
@@ -230,7 +229,7 @@ def sendMail() {
if (buildStatus != 'SUCCESS' && buildStatus != 'ABORTED') {
node('flyweight') {
sendInfraMail()
- sendKibanaMail()
+ sendKibanaMail(params)
}
}
}
@@ -246,12 +245,14 @@ def sendInfraMail() {
}
}
-def sendKibanaMail() {
+def sendKibanaMail(Map params = [:]) {
+ def config = [to: 'build-kibana@elastic.co'] + params
+
catchErrors {
def buildStatus = buildUtils.getBuildStatus()
if(params.NOTIFY_ON_FAILURE && buildStatus != 'SUCCESS' && buildStatus != 'ABORTED') {
emailext(
- to: 'build-kibana@elastic.co',
+ config.to,
subject: "${env.JOB_NAME} - Build # ${env.BUILD_NUMBER} - ${buildStatus}",
body: '${SCRIPT,template="groovy-html.template"}',
mimeType: 'text/html',
diff --git a/vars/prChanges.groovy b/vars/prChanges.groovy
index a7fe46e7bf014..5bdd62946cafc 100644
--- a/vars/prChanges.groovy
+++ b/vars/prChanges.groovy
@@ -11,6 +11,7 @@ def getSkippablePaths() {
/^.ci\/.+\.yml$/,
/^.ci\/es-snapshots\//,
/^.ci\/pipeline-library\//,
+ /^.ci\/Jenkinsfile_[^\/]+$/,
/^\.github\//,
/\.md$/,
]
diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js
index a0574dbdf36da..0e3f82792a2ba 100644
--- a/x-pack/dev-tools/jest/create_jest_config.js
+++ b/x-pack/dev-tools/jest/create_jest_config.js
@@ -58,6 +58,7 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector
`${xPackKibanaDirectory}/dev-tools/jest/setup/setup_test.js`,
`${kibanaDirectory}/src/dev/jest/setup/mocks.js`,
`${kibanaDirectory}/src/dev/jest/setup/react_testing_library.js`,
+ `${kibanaDirectory}/src/dev/jest/setup/default_timeout.js`,
],
testEnvironment: 'jest-environment-jsdom-thirteen',
testMatch: ['**/*.test.{js,mjs,ts,tsx}'],
diff --git a/x-pack/examples/ui_actions_enhanced_examples/kibana.json b/x-pack/examples/ui_actions_enhanced_examples/kibana.json
index 160352a9afd66..1bae09b488a2e 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/kibana.json
+++ b/x-pack/examples/ui_actions_enhanced_examples/kibana.json
@@ -5,7 +5,7 @@
"configPath": ["ui_actions_enhanced_examples"],
"server": false,
"ui": true,
- "requiredPlugins": ["uiActionsEnhanced", "data", "discover"],
+ "requiredPlugins": ["uiActions","uiActionsEnhanced", "data", "discover"],
"optionalPlugins": [],
"requiredBundles": [
"kibanaUtils",
diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx
index 2598d66c4976f..fd782f5468c85 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx
+++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx
@@ -10,6 +10,10 @@ import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/publ
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/ui_actions_enhanced/public';
import { ChartActionContext } from '../../../../../src/plugins/embeddable/public';
import { CollectConfigProps } from '../../../../../src/plugins/kibana_utils/public';
+import {
+ SELECT_RANGE_TRIGGER,
+ VALUE_CLICK_TRIGGER,
+} from '../../../../../src/plugins/ui_actions/public';
export type ActionContext = ChartActionContext;
@@ -19,7 +23,8 @@ export interface Config {
const SAMPLE_DASHBOARD_HELLO_WORLD_DRILLDOWN = 'SAMPLE_DASHBOARD_HELLO_WORLD_DRILLDOWN';
-export class DashboardHelloWorldDrilldown implements Drilldown {
+export class DashboardHelloWorldDrilldown
+ implements Drilldown {
public readonly id = SAMPLE_DASHBOARD_HELLO_WORLD_DRILLDOWN;
public readonly order = 6;
@@ -28,9 +33,14 @@ export class DashboardHelloWorldDrilldown implements Drilldown {
+ return [VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER];
+ }
+
private readonly ReactCollectConfig: React.FC> = ({
config,
onConfig,
+ context,
}) => (
{
+ public readonly id = SAMPLE_DASHBOARD_HELLO_WORLD_DRILLDOWN_ONLY_RANGE_SELECT;
+
+ public readonly order = 7;
+
+ public readonly getDisplayName = () => 'Say hello only for range select';
+
+ public readonly euiIcon = 'cheer';
+
+ supportedTriggers(): Array {
+ return [SELECT_RANGE_TRIGGER];
+ }
+
+ private readonly ReactCollectConfig: React.FC> = ({
+ config,
+ onConfig,
+ }) => (
+
+ onConfig({ ...config, name: event.target.value })}
+ />
+
+ );
+
+ public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig);
+
+ public readonly createConfig = () => ({
+ name: '',
+ });
+
+ public readonly isConfigValid = (
+ config: Config,
+ context: BaseActionFactoryContext
+ ): config is Config => {
+ // eslint-disable-next-line no-console
+ console.log('Showcasing, that can access action factory context:', context);
+
+ return !!config.name;
+ };
+
+ /**
+ * Showcase isCompatible. Disabled drilldown action in case if range.length === 0
+ */
+ isCompatible(config: Config, context: RangeSelectContext): Promise {
+ if (context.data.range.length === 0) return Promise.resolve(false);
+ return Promise.resolve(true);
+ }
+
+ public readonly execute = async (config: Config, context: RangeSelectContext) => {
+ alert(`Hello, ${config.name}, your selected range: ${JSON.stringify(context.data.range)}`);
+ };
+}
diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx
index ba88f49861ffe..ba8d7f395e738 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx
+++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx
@@ -13,6 +13,7 @@ import { CollectConfigContainer } from './collect_config_container';
import { SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN } from './constants';
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/ui_actions_enhanced/public';
import { txtGoToDiscover } from './i18n';
+import { APPLY_FILTER_TRIGGER } from '../../../../../src/plugins/ui_actions/public';
const isOutputWithIndexPatterns = (
output: unknown
@@ -25,7 +26,8 @@ export interface Params {
start: StartServicesGetter>;
}
-export class DashboardToDiscoverDrilldown implements Drilldown {
+export class DashboardToDiscoverDrilldown
+ implements Drilldown {
constructor(protected readonly params: Params) {}
public readonly id = SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN;
@@ -36,6 +38,10 @@ export class DashboardToDiscoverDrilldown implements Drilldown {
+ return [APPLY_FILTER_TRIGGER];
+ }
+
private readonly ReactCollectConfig: React.FC = (props) => (
);
diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts
index d8147827ed473..a10e8ad707e97 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts
+++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts
@@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { ChartActionContext } from '../../../../../src/plugins/embeddable/public';
import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../src/plugins/kibana_utils/public';
+import { ApplyGlobalFilterActionContext } from '../../../../../src/plugins/data/public';
-export type ActionContext = ChartActionContext;
+export type ActionContext = ApplyGlobalFilterActionContext;
export interface Config {
/**
diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx
index 037e017097e53..7d915ea23c66f 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx
+++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx
@@ -5,11 +5,16 @@
*/
import React from 'react';
-import { EuiFormRow, EuiSwitch, EuiFieldText, EuiCallOut, EuiSpacer } from '@elastic/eui';
+import { EuiCallOut, EuiFieldText, EuiFormRow, EuiSpacer, EuiSwitch } from '@elastic/eui';
import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public';
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/ui_actions_enhanced/public';
import { ChartActionContext } from '../../../../../src/plugins/embeddable/public';
import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../src/plugins/kibana_utils/public';
+import {
+ SELECT_RANGE_TRIGGER,
+ VALUE_CLICK_TRIGGER,
+} from '../../../../../src/plugins/ui_actions/public';
+import { ActionExecutionContext } from '../../../../../src/plugins/ui_actions/public';
function isValidUrl(url: string) {
try {
@@ -27,11 +32,13 @@ export interface Config {
openInNewTab: boolean;
}
-export type CollectConfigProps = CollectConfigPropsBase;
+type UrlTrigger = typeof VALUE_CLICK_TRIGGER | typeof SELECT_RANGE_TRIGGER;
+
+export type CollectConfigProps = CollectConfigPropsBase;
const SAMPLE_DASHBOARD_TO_URL_DRILLDOWN = 'SAMPLE_DASHBOARD_TO_URL_DRILLDOWN';
-export class DashboardToUrlDrilldown implements Drilldown {
+export class DashboardToUrlDrilldown implements Drilldown {
public readonly id = SAMPLE_DASHBOARD_TO_URL_DRILLDOWN;
public readonly order = 8;
@@ -42,7 +49,15 @@ export class DashboardToUrlDrilldown implements Drilldown
public readonly euiIcon = 'link';
- private readonly ReactCollectConfig: React.FC = ({ config, onConfig }) => (
+ supportedTriggers(): UrlTrigger[] {
+ return [VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER];
+ }
+
+ private readonly ReactCollectConfig: React.FC = ({
+ config,
+ onConfig,
+ context,
+ }) => (
<>
@@ -78,6 +93,11 @@ export class DashboardToUrlDrilldown implements Drilldown
onChange={() => onConfig({ ...config, openInNewTab: !config.openInNewTab })}
/>
+
+
+ {/* just demo how can access selected triggers*/}
+ Will be attached to triggers: {JSON.stringify(context.triggers)}
+
>
);
@@ -101,7 +121,15 @@ export class DashboardToUrlDrilldown implements Drilldown
return config.url;
};
- public readonly execute = async (config: Config, context: ActionContext) => {
+ public readonly execute = async (
+ config: Config,
+ context: ActionExecutionContext
+ ) => {
+ // Just for showcasing:
+ // we can get trigger a which caused this drilldown execution
+ // eslint-disable-next-line no-console
+ console.log(context.trigger?.id);
+
const url = await this.getHref(config, context);
if (config.openInNewTab) {
diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts
index 8034c378cc64f..7f2c9a9b3bbc8 100644
--- a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts
+++ b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts
@@ -15,6 +15,7 @@ import { DashboardToUrlDrilldown } from './dashboard_to_url_drilldown';
import { DashboardToDiscoverDrilldown } from './dashboard_to_discover_drilldown';
import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public';
import { DiscoverSetup, DiscoverStart } from '../../../../src/plugins/discover/public';
+import { DashboardHelloWorldOnlyRangeSelectDrilldown } from './dashboard_hello_world_only_range_select_drilldown';
export interface SetupDependencies {
data: DataPublicPluginSetup;
@@ -37,6 +38,7 @@ export class UiActionsEnhancedExamplesPlugin
const start = createStartServicesGetter(core.getStartServices);
uiActions.registerDrilldown(new DashboardHelloWorldDrilldown());
+ uiActions.registerDrilldown(new DashboardHelloWorldOnlyRangeSelectDrilldown());
uiActions.registerDrilldown(new DashboardToUrlDrilldown());
uiActions.registerDrilldown(new DashboardToDiscoverDrilldown({ start }));
}
diff --git a/x-pack/index.js b/x-pack/index.js
index 66fe05e8f035e..b984782df3986 100644
--- a/x-pack/index.js
+++ b/x-pack/index.js
@@ -7,9 +7,8 @@
import { xpackMain } from './legacy/plugins/xpack_main';
import { monitoring } from './legacy/plugins/monitoring';
import { security } from './legacy/plugins/security';
-import { beats } from './legacy/plugins/beats_management';
import { spaces } from './legacy/plugins/spaces';
module.exports = function (kibana) {
- return [xpackMain(kibana), monitoring(kibana), spaces(kibana), security(kibana), beats(kibana)];
+ return [xpackMain(kibana), monitoring(kibana), spaces(kibana), security(kibana)];
};
diff --git a/x-pack/legacy/plugins/beats_management/index.ts b/x-pack/legacy/plugins/beats_management/index.ts
deleted file mode 100644
index 1f04f342f9ca0..0000000000000
--- a/x-pack/legacy/plugins/beats_management/index.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import Joi from 'joi';
-import { PLUGIN } from './common/constants';
-import { CONFIG_PREFIX } from './common/constants/plugin';
-import { initServerWithKibana } from './server/kibana.index';
-import { KibanaLegacyServer } from './server/lib/adapters/framework/adapter_types';
-
-const DEFAULT_ENROLLMENT_TOKENS_TTL_S = 10 * 60; // 10 minutes
-
-export const config = Joi.object({
- enabled: Joi.boolean().default(true),
- defaultUserRoles: Joi.array().items(Joi.string()).default(['superuser']),
- encryptionKey: Joi.string().default('xpack_beats_default_encryptionKey'),
- enrollmentTokensTtlInSeconds: Joi.number()
- .integer()
- .min(1)
- .max(10 * 60 * 14) // No more then 2 weeks for security reasons
- .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S),
-}).default();
-
-export function beats(kibana: any) {
- return new kibana.Plugin({
- id: PLUGIN.ID,
- require: ['kibana', 'elasticsearch', 'xpack_main'],
- config: () => config,
- configPrefix: CONFIG_PREFIX,
- init(server: KibanaLegacyServer) {
- initServerWithKibana(server);
- },
- });
-}
diff --git a/x-pack/legacy/plugins/beats_management/server/kibana.index.ts b/x-pack/legacy/plugins/beats_management/server/kibana.index.ts
deleted file mode 100644
index dd7bc443bc603..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/kibana.index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { compose } from './lib/compose/kibana';
-import { initManagementServer } from './management_server';
-
-export const initServerWithKibana = (hapiServer: any) => {
- const libs = compose(hapiServer);
- initManagementServer(libs);
-};
diff --git a/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts
deleted file mode 100644
index 3b29e50e4465b..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { ResponseToolkit } from 'hapi';
-import { PathReporter } from 'io-ts/lib/PathReporter';
-import { get } from 'lodash';
-import { isLeft } from 'fp-ts/lib/Either';
-import { KibanaRequest, LegacyRequest } from '../../../../../../../../src/core/server';
-// @ts-ignore
-import { mirrorPluginStatus } from '../../../../../../server/lib/mirror_plugin_status';
-import {
- BackendFrameworkAdapter,
- FrameworkInfo,
- FrameworkRequest,
- FrameworkResponse,
- FrameworkRouteOptions,
- internalAuthData,
- internalUser,
- KibanaLegacyServer,
- KibanaServerRequest,
- KibanaUser,
- RuntimeFrameworkInfo,
- RuntimeKibanaUser,
- XpackInfo,
-} from './adapter_types';
-
-export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter {
- public readonly internalUser = internalUser;
- public info: null | FrameworkInfo = null;
-
- constructor(
- private readonly PLUGIN_ID: string,
- private readonly server: KibanaLegacyServer,
- private readonly CONFIG_PREFIX?: string
- ) {
- const xpackMainPlugin = this.server.plugins.xpack_main;
- const thisPlugin = this.server.plugins.beats_management;
-
- mirrorPluginStatus(xpackMainPlugin, thisPlugin);
-
- xpackMainPlugin.status.on('green', () => {
- this.xpackInfoWasUpdatedHandler(xpackMainPlugin.info);
- // Register a function that is called whenever the xpack info changes,
- // to re-compute the license check results for this plugin
- xpackMainPlugin.info
- .feature(this.PLUGIN_ID)
- .registerLicenseCheckResultsGenerator(this.xpackInfoWasUpdatedHandler);
- });
- }
-
- public on(event: 'xpack.status.green' | 'elasticsearch.status.green', cb: () => void) {
- switch (event) {
- case 'xpack.status.green':
- this.server.plugins.xpack_main.status.on('green', cb);
- case 'elasticsearch.status.green':
- this.server.plugins.elasticsearch.status.on('green', cb);
- }
- }
-
- public getSetting(settingPath: string) {
- return this.server.config().get(settingPath);
- }
-
- public log(text: string) {
- this.server.log(text);
- }
-
- public registerRoute<
- RouteRequest extends FrameworkRequest,
- RouteResponse extends FrameworkResponse
- >(route: FrameworkRouteOptions) {
- this.server.route({
- handler: async (request: KibanaServerRequest, h: ResponseToolkit) => {
- // Note, RuntimeKibanaServerRequest is avalaible to validate request, and its type *is* KibanaServerRequest
- // but is not used here for perf reasons. It's value here is not high enough...
- return await route.handler(await this.wrapRequest(request), h);
- },
- method: route.method,
- path: route.path,
- config: route.config,
- });
- }
-
- private async wrapRequest(
- req: KibanaServerRequest
- ): Promise> {
- const { params, payload, query, headers, info } = req;
-
- let isAuthenticated = headers.authorization != null;
- let user;
- if (isAuthenticated) {
- user = await this.getUser(req);
- if (!user) {
- isAuthenticated = false;
- }
- }
- return {
- user:
- isAuthenticated && user
- ? {
- kind: 'authenticated',
- [internalAuthData]: headers,
- ...user,
- }
- : {
- kind: 'unauthenticated',
- },
- headers,
- info,
- params,
- payload,
- query,
- };
- }
-
- private async getUser(request: KibanaServerRequest): Promise {
- const user = this.server.newPlatform.setup.plugins.security?.authc.getCurrentUser(
- KibanaRequest.from((request as unknown) as LegacyRequest)
- );
- if (!user) {
- return null;
- }
- const assertKibanaUser = RuntimeKibanaUser.decode(user);
- if (isLeft(assertKibanaUser)) {
- throw new Error(
- `Error parsing user info in ${this.PLUGIN_ID}, ${
- PathReporter.report(assertKibanaUser)[0]
- }`
- );
- }
-
- return user;
- }
-
- private xpackInfoWasUpdatedHandler = (xpackInfo: XpackInfo) => {
- let xpackInfoUnpacked: FrameworkInfo;
-
- // If, for some reason, we cannot get the license information
- // from Elasticsearch, assume worst case and disable
- if (!xpackInfo || !xpackInfo.isAvailable()) {
- this.info = null;
- return;
- }
-
- try {
- xpackInfoUnpacked = {
- kibana: {
- version: get(this.server, 'plugins.kibana.status.plugin.version', 'unknown'),
- },
- license: {
- type: xpackInfo.license.getType(),
- expired: !xpackInfo.license.isActive(),
- expiry_date_in_millis:
- xpackInfo.license.getExpiryDateInMillis() !== undefined
- ? xpackInfo.license.getExpiryDateInMillis()
- : -1,
- },
- security: {
- enabled: !!xpackInfo.feature('security') && xpackInfo.feature('security').isEnabled(),
- available: !!xpackInfo.feature('security'),
- },
- watcher: {
- enabled: !!xpackInfo.feature('watcher') && xpackInfo.feature('watcher').isEnabled(),
- available: !!xpackInfo.feature('watcher'),
- },
- };
- } catch (e) {
- this.server.log(`Error accessing required xPackInfo in ${this.PLUGIN_ID} Kibana adapter`);
- throw e;
- }
-
- const assertData = RuntimeFrameworkInfo.decode(xpackInfoUnpacked);
- if (isLeft(assertData)) {
- throw new Error(
- `Error parsing xpack info in ${this.PLUGIN_ID}, ${PathReporter.report(assertData)[0]}`
- );
- }
- this.info = xpackInfoUnpacked;
-
- return {
- security: xpackInfoUnpacked.security,
- settings: this.getSetting(this.CONFIG_PREFIX || this.PLUGIN_ID),
- };
- };
-}
diff --git a/x-pack/legacy/plugins/beats_management/server/lib/framework.ts b/x-pack/legacy/plugins/beats_management/server/lib/framework.ts
deleted file mode 100644
index 96a06929073e5..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/lib/framework.ts
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { ResponseObject, ResponseToolkit } from 'hapi';
-import { difference } from 'lodash';
-import { BaseReturnType } from '../../common/return_types';
-import {
- BackendFrameworkAdapter,
- FrameworkRequest,
- FrameworkResponse,
-} from './adapters/framework/adapter_types';
-
-export class BackendFrameworkLib {
- public log = this.adapter.log;
- public on = this.adapter.on.bind(this.adapter);
- public internalUser = this.adapter.internalUser;
- constructor(private readonly adapter: BackendFrameworkAdapter) {
- this.validateConfig();
- }
-
- public registerRoute<
- RouteRequest extends FrameworkRequest,
- RouteResponse extends FrameworkResponse
- >(route: {
- path: string;
- method: string | string[];
- licenseRequired?: string[];
- requiredRoles?: string[];
- handler: (request: FrameworkRequest) => Promise;
- config?: {};
- }) {
- this.adapter.registerRoute({
- ...route,
- handler: this.wrapErrors(
- this.wrapRouteWithSecurity(route.handler, route.licenseRequired || [], route.requiredRoles)
- ),
- });
- }
-
- public getSetting(setting: 'encryptionKey'): string;
- public getSetting(setting: 'enrollmentTokensTtlInSeconds'): number;
- public getSetting(setting: 'defaultUserRoles'): string[];
- public getSetting(
- setting: 'encryptionKey' | 'enrollmentTokensTtlInSeconds' | 'defaultUserRoles'
- ) {
- return this.adapter.getSetting(`xpack.beats.${setting}`);
- }
-
- /**
- * Expired `null` happens when we have no xpack info
- */
- public get license() {
- return {
- type: this.adapter.info ? this.adapter.info.license.type : 'unknown',
- expired: this.adapter.info ? this.adapter.info.license.expired : null,
- };
- }
-
- public get securityIsEnabled() {
- return this.adapter.info ? this.adapter.info.security.enabled : false;
- }
-
- private validateConfig() {
- const encryptionKey = this.adapter.getSetting('xpack.beats.encryptionKey');
-
- if (!encryptionKey) {
- this.adapter.log(
- 'Using a default encryption key for xpack.beats.encryptionKey. It is recommended that you set xpack.beats.encryptionKey in kibana.yml with a unique token'
- );
- }
- }
-
- private wrapRouteWithSecurity(
- handler: (request: FrameworkRequest) => Promise,
- requiredLicense: string[],
- requiredRoles?: string[]
- ): (request: FrameworkRequest) => Promise {
- return async (request: FrameworkRequest) => {
- if (
- requiredLicense.length > 0 &&
- (this.license.expired || !requiredLicense.includes(this.license.type))
- ) {
- return {
- error: {
- message: `Your ${this.license.type} license does not support this API or is expired. Please upgrade your license.`,
- code: 403,
- },
- success: false,
- };
- }
-
- if (requiredRoles) {
- if (request.user.kind !== 'authenticated') {
- return {
- error: {
- message: `Request must be authenticated`,
- code: 403,
- },
- success: false,
- };
- }
-
- if (
- request.user.kind === 'authenticated' &&
- !request.user.roles.includes('superuser') &&
- difference(requiredRoles, request.user.roles).length !== 0
- ) {
- return {
- error: {
- message: `Request must be authenticated by a user with one of the following user roles: ${requiredRoles.join(
- ','
- )}`,
- code: 403,
- },
- success: false,
- };
- }
- }
- return await handler(request);
- };
- }
- private wrapErrors(
- handler: (request: FrameworkRequest) => Promise
- ): (request: FrameworkRequest, h: ResponseToolkit) => Promise {
- return async (request: FrameworkRequest, h: ResponseToolkit) => {
- try {
- const result = await handler(request);
- if (!result.error) {
- return h.response(result);
- }
- return h
- .response({
- error: result.error,
- success: false,
- })
- .code(result.error.code || 400);
- } catch (err) {
- let statusCode = err.statusCode;
-
- // This is the only known non-status code error in the system, but just in case we have an else
- if (!statusCode && (err.message as string).includes('Invalid user type')) {
- statusCode = 403;
- } else {
- statusCode = 500;
- }
-
- if (statusCode === 403) {
- return h
- .response({
- error: {
- message: 'Insufficient user permissions for managing Beats configuration',
- code: 403,
- },
- success: false,
- })
- .code(403);
- }
-
- return h
- .response({
- error: {
- message: err.message,
- code: statusCode,
- },
- success: false,
- })
- .code(statusCode);
- }
- };
- }
-}
diff --git a/x-pack/legacy/plugins/beats_management/server/management_server.ts b/x-pack/legacy/plugins/beats_management/server/management_server.ts
deleted file mode 100644
index 1073251949028..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/management_server.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { INDEX_NAMES } from '../common/constants/index_names';
-import { beatsIndexTemplate } from './index_templates';
-import { CMServerLibs } from './lib/types';
-import { createGetBeatConfigurationRoute } from './rest_api/beats/configuration';
-import { createBeatEnrollmentRoute } from './rest_api/beats/enroll';
-import { beatEventsRoute } from './rest_api/beats/events';
-import { createGetBeatRoute } from './rest_api/beats/get';
-import { createListAgentsRoute } from './rest_api/beats/list';
-import { createTagAssignmentsRoute } from './rest_api/beats/tag_assignment';
-import { createTagRemovalsRoute } from './rest_api/beats/tag_removal';
-import { createBeatUpdateRoute } from './rest_api/beats/update';
-import { createDeleteConfidurationsRoute } from './rest_api/configurations/delete';
-import { createGetConfigurationBlocksRoute } from './rest_api/configurations/get';
-import { upsertConfigurationRoute } from './rest_api/configurations/upsert';
-import { createAssignableTagsRoute } from './rest_api/tags/assignable';
-import { createDeleteTagsWithIdsRoute } from './rest_api/tags/delete';
-import { createGetTagsWithIdsRoute } from './rest_api/tags/get';
-import { createListTagsRoute } from './rest_api/tags/list';
-import { createSetTagRoute } from './rest_api/tags/set';
-import { createTokensRoute } from './rest_api/tokens/create';
-
-export const initManagementServer = (libs: CMServerLibs) => {
- if (libs.database) {
- libs.framework.on('elasticsearch.status.green', async () => {
- await libs.database!.putTemplate(INDEX_NAMES.BEATS, beatsIndexTemplate);
- });
- }
-
- libs.framework.registerRoute(createGetBeatRoute(libs));
- libs.framework.registerRoute(createGetTagsWithIdsRoute(libs));
- libs.framework.registerRoute(createListTagsRoute(libs));
- libs.framework.registerRoute(createDeleteTagsWithIdsRoute(libs));
- libs.framework.registerRoute(createGetBeatConfigurationRoute(libs));
- libs.framework.registerRoute(createTagAssignmentsRoute(libs));
- libs.framework.registerRoute(createListAgentsRoute(libs));
- libs.framework.registerRoute(createTagRemovalsRoute(libs));
- libs.framework.registerRoute(createBeatEnrollmentRoute(libs));
- libs.framework.registerRoute(createSetTagRoute(libs));
- libs.framework.registerRoute(createTokensRoute(libs));
- libs.framework.registerRoute(createBeatUpdateRoute(libs));
- libs.framework.registerRoute(createDeleteConfidurationsRoute(libs));
- libs.framework.registerRoute(createGetConfigurationBlocksRoute(libs));
- libs.framework.registerRoute(upsertConfigurationRoute(libs));
- libs.framework.registerRoute(createAssignableTagsRoute(libs));
- libs.framework.registerRoute(beatEventsRoute(libs));
-};
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/configuration.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/beats/configuration.ts
deleted file mode 100644
index f279a51b2bc1b..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/configuration.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import Joi from 'joi';
-import { ConfigurationBlock } from '../../../common/domain_types';
-import { BaseReturnType, ReturnTypeList } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-export const createGetBeatConfigurationRoute = (libs: CMServerLibs) => ({
- method: 'GET',
- path: '/api/beats/agent/{beatId}/configuration',
- config: {
- validate: {
- headers: Joi.object({
- 'kbn-beats-access-token': Joi.string().required(),
- }).options({ allowUnknown: true }),
- },
- auth: false,
- },
- handler: async (
- request: FrameworkRequest
- ): Promise> => {
- const beatId = request.params.beatId;
- const accessToken = request.headers['kbn-beats-access-token'];
-
- let configurationBlocks: ConfigurationBlock[];
- const beat = await libs.beats.getById(libs.framework.internalUser, beatId);
- if (beat === null) {
- return { error: { message: `Beat "${beatId}" not found`, code: 404 }, success: false };
- }
-
- const isAccessTokenValid = beat.access_token === accessToken;
- if (!isAccessTokenValid) {
- return { error: { message: 'Invalid access token', code: 401 }, success: false };
- }
-
- await libs.beats.update(libs.framework.internalUser, beat.id, {
- last_checkin: new Date(),
- });
-
- if (beat.tags) {
- const result = await libs.configurationBlocks.getForTags(
- libs.framework.internalUser,
- beat.tags,
- -1
- );
-
- configurationBlocks = result.blocks;
- } else {
- configurationBlocks = [];
- }
-
- return {
- list: configurationBlocks,
- success: true,
- };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/enroll.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/beats/enroll.ts
deleted file mode 100644
index 916cfad4102d0..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/enroll.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import Joi from 'joi';
-import { omit } from 'lodash';
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { BaseReturnType, ReturnTypeCreate } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { BeatEnrollmentStatus, CMServerLibs } from '../../lib/types';
-
-// TODO: write to Kibana audit log file https://github.com/elastic/kibana/issues/26024
-export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({
- method: 'POST',
- path: '/api/beats/agent/{beatId}',
- licenseRequired: REQUIRED_LICENSES,
- config: {
- auth: false,
- validate: {
- headers: Joi.object({
- 'kbn-beats-enrollment-token': Joi.string().required(),
- }).options({
- allowUnknown: true,
- }),
- payload: Joi.object({
- host_name: Joi.string().required(),
- name: Joi.string().required(),
- type: Joi.string().required(),
- version: Joi.string().required(),
- }).required(),
- },
- },
- handler: async (
- request: FrameworkRequest
- ): Promise> => {
- const { beatId } = request.params;
- const enrollmentToken = request.headers['kbn-beats-enrollment-token'];
-
- const { status, accessToken } = await libs.beats.enrollBeat(
- enrollmentToken,
- beatId,
- request.info.remoteAddress,
- omit(request.payload, 'enrollment_token')
- );
-
- switch (status) {
- case BeatEnrollmentStatus.ExpiredEnrollmentToken:
- return {
- error: { message: BeatEnrollmentStatus.ExpiredEnrollmentToken, code: 400 },
- success: false,
- };
-
- case BeatEnrollmentStatus.InvalidEnrollmentToken:
- return {
- error: { message: BeatEnrollmentStatus.InvalidEnrollmentToken, code: 400 },
- success: false,
- };
- case BeatEnrollmentStatus.Success:
- default:
- return {
- item: accessToken,
- action: 'created',
- success: true,
- };
- }
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/events.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/beats/events.ts
deleted file mode 100644
index 65d7e9979b9ca..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/events.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import Joi from 'joi';
-import { BaseReturnType, ReturnTypeBulkAction } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-export const beatEventsRoute = (libs: CMServerLibs) => ({
- method: 'POST',
- path: '/api/beats/{beatId}/events',
- config: {
- validate: {
- headers: Joi.object({
- 'kbn-beats-access-token': Joi.string().required(),
- }).options({ allowUnknown: true }),
- },
- auth: false,
- },
- handler: async (request: FrameworkRequest): Promise => {
- const beatId = request.params.beatId;
- const events = request.payload;
- const accessToken = request.headers['kbn-beats-access-token'];
-
- const beat = await libs.beats.getById(libs.framework.internalUser, beatId);
- if (beat === null) {
- return { error: { message: `Beat "${beatId}" not found`, code: 400 }, success: false };
- }
-
- const isAccessTokenValid = beat.access_token === accessToken;
- if (!isAccessTokenValid) {
- return { error: { message: `Invalid access token`, code: 401 }, success: false };
- }
-
- const results = await libs.beatEvents.log(libs.framework.internalUser, beat.id, events);
-
- return {
- results,
- success: true,
- };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/get.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/beats/get.ts
deleted file mode 100644
index 874e66bb8a533..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/get.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { CMBeat } from '../../../common/domain_types';
-import { BaseReturnType, ReturnTypeGet } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-export const createGetBeatRoute = (libs: CMServerLibs) => ({
- method: 'GET',
- path: '/api/beats/agent/{beatId}/{token?}',
- requiredRoles: ['beats_admin'],
- handler: async (request: FrameworkRequest): Promise> => {
- const beatId = request.params.beatId;
-
- let beat: CMBeat | null;
- if (beatId === 'unknown') {
- beat = await libs.beats.getByEnrollmentToken(request.user, request.params.token);
- if (beat === null) {
- return { success: false };
- }
- } else {
- beat = await libs.beats.getById(request.user, beatId);
- if (beat === null) {
- return { error: { message: 'Beat not found', code: 404 }, success: false };
- }
- }
-
- delete beat.access_token;
-
- return {
- item: beat,
- success: true,
- };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/list.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/beats/list.ts
deleted file mode 100644
index 74fb98fc877cc..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/list.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import * as Joi from 'joi';
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { CMBeat } from '../../../common/domain_types';
-import { ReturnTypeList } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-export const createListAgentsRoute = (libs: CMServerLibs) => ({
- method: 'GET',
- path: '/api/beats/agents/{listByAndValue*}',
- requiredRoles: ['beats_admin'],
- licenseRequired: REQUIRED_LICENSES,
-
- validate: {
- headers: Joi.object({
- 'kbn-beats-enrollment-token': Joi.string().required(),
- }).options({
- allowUnknown: true,
- }),
- query: Joi.object({
- ESQuery: Joi.string(),
- }),
- },
- handler: async (request: FrameworkRequest): Promise> => {
- const listByAndValueParts = request.params.listByAndValue
- ? request.params.listByAndValue.split('/')
- : [];
- let listBy: 'tag' | null = null;
- let listByValue: string | null = null;
-
- if (listByAndValueParts.length === 2) {
- listBy = listByAndValueParts[0];
- listByValue = listByAndValueParts[1];
- }
-
- let beats: CMBeat[];
-
- switch (listBy) {
- case 'tag':
- beats = await libs.beats.getAllWithTag(request.user, listByValue || '');
- break;
-
- default:
- beats = await libs.beats.getAll(
- request.user,
- request.query && request.query.ESQuery ? JSON.parse(request.query.ESQuery) : undefined
- );
-
- break;
- }
-
- return { list: beats, success: true, page: -1, total: -1 };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/tag_assignment.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/beats/tag_assignment.ts
deleted file mode 100644
index 974b2822fbd92..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/tag_assignment.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import Joi from 'joi';
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { ReturnTypeBulkAction } from '../../../common/return_types';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { BeatsTagAssignment } from '../../../../../../plugins/beats_management/public/lib/adapters/beats/adapter_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-// TODO: write to Kibana audit log file https://github.com/elastic/kibana/issues/26024
-export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({
- method: 'POST',
- path: '/api/beats/agents_tags/assignments',
- licenseRequired: REQUIRED_LICENSES,
- requiredRoles: ['beats_admin'],
- config: {
- validate: {
- payload: Joi.object({
- assignments: Joi.array().items(
- Joi.object({
- beatId: Joi.string().required(),
- tag: Joi.string().required(),
- })
- ),
- }).required(),
- },
- },
- handler: async (request: FrameworkRequest): Promise => {
- const { assignments }: { assignments: BeatsTagAssignment[] } = request.payload;
-
- const response = await libs.beats.assignTagsToBeats(request.user, assignments);
-
- return {
- success: true,
- results: response.assignments.map((assignment) => ({
- success: assignment.status && assignment.status >= 200 && assignment.status < 300,
- error:
- !assignment.status || assignment.status >= 300
- ? {
- code: assignment.status || 400,
- message: assignment.result,
- }
- : undefined,
- result:
- assignment.status && assignment.status >= 200 && assignment.status < 300
- ? {
- message: assignment.result,
- }
- : undefined,
- })),
- } as ReturnTypeBulkAction;
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/tag_removal.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/beats/tag_removal.ts
deleted file mode 100644
index 3bbc32dc5748b..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/tag_removal.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import Joi from 'joi';
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { ReturnTypeBulkAction } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-// TODO: write to Kibana audit log file https://github.com/elastic/kibana/issues/26024
-export const createTagRemovalsRoute = (libs: CMServerLibs) => ({
- method: 'POST',
- path: '/api/beats/agents_tags/removals',
- licenseRequired: REQUIRED_LICENSES,
- requiredRoles: ['beats_admin'],
- config: {
- validate: {
- payload: Joi.object({
- removals: Joi.array().items(
- Joi.object({
- beatId: Joi.string().required(),
- tag: Joi.string().required(),
- })
- ),
- }).required(),
- },
- },
- handler: async (request: FrameworkRequest): Promise => {
- const { removals } = request.payload;
-
- const response = await libs.beats.removeTagsFromBeats(request.user, removals);
-
- return {
- success: true,
- results: response.removals.map((removal) => ({
- success: removal.status && removal.status >= 200 && removal.status < 300,
- error:
- !removal.status || removal.status >= 300
- ? {
- code: removal.status || 400,
- message: removal.result,
- }
- : undefined,
- result:
- removal.status && removal.status >= 200 && removal.status < 300
- ? {
- message: removal.result,
- }
- : undefined,
- })),
- } as ReturnTypeBulkAction;
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/update.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/beats/update.ts
deleted file mode 100644
index 2859083e83386..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/beats/update.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import Joi from 'joi';
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { CMBeat } from '../../../common/domain_types';
-import { BaseReturnType, ReturnTypeUpdate } from '../../../common/return_types';
-import { FrameworkRequest, internalUser } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-// TODO: write to Kibana audit log file (include who did the verification as well) https://github.com/elastic/kibana/issues/26024
-export const createBeatUpdateRoute = (libs: CMServerLibs) => ({
- method: 'PUT',
- path: '/api/beats/agent/{beatId}',
- licenseRequired: REQUIRED_LICENSES,
- requiredRoles: ['beats_admin'],
- config: {
- validate: {
- headers: Joi.object({
- 'kbn-beats-access-token': Joi.string(),
- }).options({
- allowUnknown: true,
- }),
- params: Joi.object({
- beatId: Joi.string(),
- }),
- payload: Joi.object({
- active: Joi.bool(),
- ephemeral_id: Joi.string(),
- host_name: Joi.string(),
- local_configuration_yml: Joi.string(),
- metadata: Joi.object(),
- name: Joi.string(),
- type: Joi.string(),
- version: Joi.string(),
- }),
- },
- },
- handler: async (
- request: FrameworkRequest
- ): Promise> => {
- const { beatId } = request.params;
- const accessToken = request.headers['kbn-beats-access-token'];
- const remoteAddress = request.info.remoteAddress;
- const userOrToken = accessToken || request.user;
-
- if (request.user.kind === 'unauthenticated' && request.payload.active !== undefined) {
- return {
- error: {
- message: 'access-token is not a valid auth type to change beat status',
- code: 401,
- },
- success: false,
- };
- }
-
- const status = await libs.beats.update(userOrToken, beatId, {
- ...request.payload,
- host_ip: remoteAddress,
- });
-
- switch (status) {
- case 'beat-not-found':
- return {
- error: {
- message: 'Beat not found',
- code: 404,
- },
- success: false,
- };
- case 'invalid-access-token':
- return {
- error: {
- message: 'Invalid access token',
- code: 401,
- },
- success: false,
- };
- }
-
- const beat = await libs.beats.getById(internalUser, beatId);
-
- if (!beat) {
- return {
- error: {
- message: 'Beat not found',
- code: 404,
- },
- success: false,
- };
- }
-
- return {
- item: beat,
- action: 'updated',
- success: true,
- };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/configurations/delete.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/configurations/delete.ts
deleted file mode 100644
index b7d430fb18c01..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/configurations/delete.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { ReturnTypeBulkDelete } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-export const createDeleteConfidurationsRoute = (libs: CMServerLibs) => ({
- method: 'DELETE',
- path: '/api/beats/configurations/{ids}',
- requiredRoles: ['beats_admin'],
- licenseRequired: REQUIRED_LICENSES,
- handler: async (request: FrameworkRequest): Promise => {
- const idString: string = request.params.ids;
- const ids = idString.split(',').filter((id: string) => id.length > 0);
-
- const results = await libs.configurationBlocks.delete(request.user, ids);
-
- return {
- success: true,
- results: results.map((result) => ({
- success: result.success,
- action: 'deleted',
- error: result.success ? undefined : { message: result.reason },
- })),
- } as ReturnTypeBulkDelete;
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/configurations/get.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/configurations/get.ts
deleted file mode 100644
index df534f74239ed..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/configurations/get.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { ConfigurationBlock } from '../../../common/domain_types';
-import { ReturnTypeList } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-export const createGetConfigurationBlocksRoute = (libs: CMServerLibs) => ({
- method: 'GET',
- path: '/api/beats/configurations/{tagIds}/{page?}',
- requiredRoles: ['beats_admin'],
- licenseRequired: REQUIRED_LICENSES,
- handler: async (request: FrameworkRequest): Promise> => {
- const tagIdString: string = request.params.tagIds;
- const tagIds = tagIdString.split(',').filter((id: string) => id.length > 0);
-
- const result = await libs.configurationBlocks.getForTags(
- request.user,
- tagIds,
- parseInt(request.params.page, 10),
- 5
- );
-
- return { page: result.page, total: result.total, list: result.blocks, success: true };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/configurations/upsert.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/configurations/upsert.ts
deleted file mode 100644
index fb62800594d0a..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/configurations/upsert.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { PathReporter } from 'io-ts/lib/PathReporter';
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import Joi from 'joi';
-import { isLeft } from 'fp-ts/lib/Either';
-import { REQUIRED_LICENSES } from '../../../common/constants';
-import {
- ConfigurationBlock,
- createConfigurationBlockInterface,
-} from '../../../common/domain_types';
-import { ReturnTypeBulkUpsert } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-// TODO: write to Kibana audit log file
-export const upsertConfigurationRoute = (libs: CMServerLibs) => ({
- method: 'PUT',
- path: '/api/beats/configurations',
- licenseRequired: REQUIRED_LICENSES,
- requiredRoles: ['beats_admin'],
- config: {
- validate: {
- payload: Joi.array().items(Joi.object({}).unknown(true)),
- },
- },
- handler: async (request: FrameworkRequest): Promise => {
- const result = await Promise.all(
- request.payload.map(async (block: ConfigurationBlock) => {
- const assertData = createConfigurationBlockInterface().decode(block);
- if (isLeft(assertData)) {
- return {
- error: `Error parsing block info, ${PathReporter.report(assertData)[0]}`,
- };
- }
-
- const { blockID, success, error } = await libs.configurationBlocks.save(
- request.user,
- block
- );
- if (error) {
- return { success, error };
- }
-
- return { success, blockID };
- })
- );
-
- return {
- results: result.map((r) => ({
- success: r.success as boolean,
- // TODO: we need to surface this data, not hard coded
- action: 'created' as 'created' | 'updated',
- })),
- success: true,
- };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/tags/assignable.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/tags/assignable.ts
deleted file mode 100644
index 88a322c03790f..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/tags/assignable.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { flatten } from 'lodash';
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { BeatTag } from '../../../common/domain_types';
-import { ReturnTypeBulkGet } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-export const createAssignableTagsRoute = (libs: CMServerLibs) => ({
- method: 'GET',
- path: '/api/beats/tags/assignable/{beatIds}',
- requiredRoles: ['beats_admin'],
- licenseRequired: REQUIRED_LICENSES,
- handler: async (request: FrameworkRequest): Promise> => {
- const beatIdString: string = request.params.beatIds;
- const beatIds = beatIdString.split(',').filter((id: string) => id.length > 0);
-
- const beats = await libs.beats.getByIds(request.user, beatIds);
- const tags = await libs.tags.getNonConflictingTags(
- request.user,
- flatten(beats.map((beat) => beat.tags))
- );
-
- return {
- items: tags,
- success: true,
- };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/tags/delete.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/tags/delete.ts
deleted file mode 100644
index 3e65f271c0fb0..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/tags/delete.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { ReturnTypeBulkDelete } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-export const createDeleteTagsWithIdsRoute = (libs: CMServerLibs) => ({
- method: 'DELETE',
- path: '/api/beats/tags/{tagIds}',
- requiredRoles: ['beats_admin'],
- licenseRequired: REQUIRED_LICENSES,
- handler: async (request: FrameworkRequest): Promise => {
- const tagIdString: string = request.params.tagIds;
- const tagIds = tagIdString.split(',').filter((id: string) => id.length > 0);
-
- const success = await libs.tags.delete(request.user, tagIds);
-
- return {
- results: tagIds.map(() => ({
- success,
- action: 'deleted',
- })),
- success,
- } as ReturnTypeBulkDelete;
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/tags/get.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/tags/get.ts
deleted file mode 100644
index 37f8e1169fa6c..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/tags/get.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { BeatTag } from '../../../common/domain_types';
-import { ReturnTypeBulkGet } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-export const createGetTagsWithIdsRoute = (libs: CMServerLibs) => ({
- method: 'GET',
- path: '/api/beats/tags/{tagIds}',
- requiredRoles: ['beats_admin'],
- licenseRequired: REQUIRED_LICENSES,
- handler: async (request: FrameworkRequest): Promise> => {
- const tagIdString: string = request.params.tagIds;
- const tagIds = tagIdString.split(',').filter((id: string) => id.length > 0);
-
- const tags = await libs.tags.getWithIds(request.user, tagIds);
-
- return {
- items: tags,
- success: true,
- };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/tags/list.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/tags/list.ts
deleted file mode 100644
index eb5570273960f..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/tags/list.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import * as Joi from 'joi';
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { BeatTag } from '../../../common/domain_types';
-import { ReturnTypeList } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-export const createListTagsRoute = (libs: CMServerLibs) => ({
- method: 'GET',
- path: '/api/beats/tags',
- requiredRoles: ['beats_admin'],
- licenseRequired: REQUIRED_LICENSES,
- validate: {
- headers: Joi.object({
- 'kbn-beats-enrollment-token': Joi.string().required(),
- }).options({
- allowUnknown: true,
- }),
- query: Joi.object({
- ESQuery: Joi.string(),
- }),
- },
- handler: async (request: FrameworkRequest): Promise> => {
- const tags = await libs.tags.getAll(
- request.user,
- request.query && request.query.ESQuery ? JSON.parse(request.query.ESQuery) : undefined
- );
-
- return { list: tags, success: true, page: -1, total: -1 };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/tags/set.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/tags/set.ts
deleted file mode 100644
index f2c4b17007f10..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/tags/set.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import Joi from 'joi';
-import { get } from 'lodash';
-import { REQUIRED_LICENSES } from '../../../common/constants';
-import { BeatTag } from '../../../common/domain_types';
-import { ReturnTypeUpsert } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-// TODO: write to Kibana audit log file
-export const createSetTagRoute = (libs: CMServerLibs) => ({
- method: 'PUT',
- path: '/api/beats/tag/{tagId}',
- licenseRequired: REQUIRED_LICENSES,
- requiredRoles: ['beats_admin'],
- config: {
- validate: {
- params: Joi.object({
- tagId: Joi.string(),
- }),
- payload: Joi.object({
- color: Joi.string(),
- name: Joi.string(),
- }),
- },
- },
- handler: async (request: FrameworkRequest): Promise> => {
- const defaultConfig = {
- id: request.params.tagId,
- name: request.params.tagId,
- color: '#DD0A73',
- hasConfigurationBlocksTypes: [],
- };
- const config = { ...defaultConfig, ...get(request, 'payload', {}) };
-
- const id = await libs.tags.upsertTag(request.user, config);
- const tag = await libs.tags.getWithIds(request.user, [id]);
-
- // TODO the action needs to be surfaced
- return { success: true, item: tag[0], action: 'created' };
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/server/rest_api/tokens/create.ts b/x-pack/legacy/plugins/beats_management/server/rest_api/tokens/create.ts
deleted file mode 100644
index 571d2b4a4947c..0000000000000
--- a/x-pack/legacy/plugins/beats_management/server/rest_api/tokens/create.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import Joi from 'joi';
-import { get } from 'lodash';
-import { REQUIRED_LICENSES } from '../../../common/constants/security';
-import { BaseReturnType, ReturnTypeBulkCreate } from '../../../common/return_types';
-import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
-import { CMServerLibs } from '../../lib/types';
-
-// TODO: write to Kibana audit log file
-const DEFAULT_NUM_TOKENS = 1;
-export const createTokensRoute = (libs: CMServerLibs) => ({
- method: 'POST',
- path: '/api/beats/enrollment_tokens',
- licenseRequired: REQUIRED_LICENSES,
- requiredRoles: ['beats_admin'],
- config: {
- validate: {
- payload: Joi.object({
- num_tokens: Joi.number().optional().default(DEFAULT_NUM_TOKENS).min(1),
- }).allow(null),
- },
- },
- handler: async (
- request: FrameworkRequest
- ): Promise> => {
- const numTokens = get(request, 'payload.num_tokens', DEFAULT_NUM_TOKENS);
-
- try {
- const tokens = await libs.tokens.createEnrollmentTokens(request.user, numTokens);
- return {
- results: tokens.map((token) => ({
- item: token,
- success: true,
- action: 'created',
- })),
- success: true,
- };
- } catch (err) {
- libs.framework.log(err.message);
- return {
- error: {
- message: 'An error occured, please check your Kibana logs',
- code: 500,
- },
- success: false,
- };
- }
- },
-});
diff --git a/x-pack/legacy/plugins/beats_management/tsconfig.json b/x-pack/legacy/plugins/beats_management/tsconfig.json
deleted file mode 100644
index 7ade047bad32e..0000000000000
--- a/x-pack/legacy/plugins/beats_management/tsconfig.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extends": "../../../tsconfig.json",
- "exclude": ["**/node_modules/**"],
- "paths": {
- "react": ["../../../node_modules/@types/react"]
- }
-}
diff --git a/x-pack/package.json b/x-pack/package.json
index 2b52646e0f748..5333c67f6ac0f 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -44,9 +44,9 @@
"@storybook/addon-storyshots": "^5.3.19",
"@storybook/react": "^5.3.19",
"@storybook/theming": "^5.3.19",
+ "@testing-library/jest-dom": "^5.8.0",
"@testing-library/react": "^9.3.2",
"@testing-library/react-hooks": "^3.2.1",
- "@testing-library/jest-dom": "^5.8.0",
"@types/angular": "^1.6.56",
"@types/archiver": "^3.1.0",
"@types/base64-js": "^1.2.5",
@@ -72,8 +72,9 @@
"@types/gulp": "^4.0.6",
"@types/hapi__wreck": "^15.0.1",
"@types/he": "^1.1.1",
- "@types/hoist-non-react-statics": "^3.3.1",
"@types/history": "^4.7.3",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "@types/http-proxy": "^1.17.4",
"@types/jest": "^25.2.3",
"@types/jest-specific-snapshot": "^0.5.4",
"@types/joi": "^13.4.2",
@@ -82,7 +83,7 @@
"@types/jsdom": "^16.2.3",
"@types/json-stable-stringify": "^1.0.32",
"@types/jsonwebtoken": "^7.2.8",
- "@types/lodash": "^4.14.155",
+ "@types/lodash": "^4.14.159",
"@types/mapbox-gl": "^1.9.1",
"@types/memoize-one": "^4.1.0",
"@types/mime": "^2.0.1",
@@ -94,6 +95,7 @@
"@types/object-hash": "^1.3.0",
"@types/papaparse": "^5.0.3",
"@types/pngjs": "^3.3.2",
+ "@types/pretty-ms": "^5.0.0",
"@types/prop-types": "^15.5.3",
"@types/proper-lockfile": "^3.0.1",
"@types/puppeteer": "^1.20.1",
@@ -109,6 +111,7 @@
"@types/redux-actions": "^2.6.1",
"@types/set-value": "^2.0.0",
"@types/sinon": "^7.0.13",
+ "@types/stats-lite": "^2.2.0",
"@types/styled-components": "^5.1.0",
"@types/supertest": "^2.0.5",
"@types/tar-fs": "^1.16.1",
@@ -116,11 +119,9 @@
"@types/tinycolor2": "^1.4.1",
"@types/use-resize-observer": "^6.0.0",
"@types/uuid": "^3.4.4",
+ "@types/webpack-env": "^1.15.2",
"@types/xml-crypto": "^1.4.0",
"@types/xml2js": "^0.4.5",
- "@types/stats-lite": "^2.2.0",
- "@types/pretty-ms": "^5.0.0",
- "@types/webpack-env": "^1.15.2",
"@welldone-software/why-did-you-render": "^4.0.0",
"abab": "^1.0.4",
"autoprefixer": "^9.7.4",
@@ -205,7 +206,7 @@
"@elastic/apm-rum-react": "^1.2.2",
"@elastic/datemath": "5.0.3",
"@elastic/ems-client": "7.9.3",
- "@elastic/eui": "26.3.1",
+ "@elastic/eui": "27.4.0",
"@elastic/filesaver": "1.1.2",
"@elastic/maki": "6.3.0",
"@elastic/node-crypto": "1.2.1",
@@ -227,6 +228,7 @@
"@turf/circle": "6.0.1",
"@turf/distance": "6.0.1",
"@turf/helpers": "6.0.1",
+ "@types/http-proxy-agent": "^2.0.2",
"angular": "^1.8.0",
"angular-resource": "1.8.0",
"angular-sanitize": "1.8.0",
diff --git a/x-pack/plugins/actions/server/builtin_action_types/case/types.ts b/x-pack/plugins/actions/server/builtin_action_types/case/types.ts
index de96864d0b295..1030e3d9c5d8e 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/case/types.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/case/types.ts
@@ -9,6 +9,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { TypeOf } from '@kbn/config-schema';
+import { Logger } from '../../../../../../src/core/server';
import {
ExternalIncidentServiceConfigurationSchema,
@@ -122,7 +123,12 @@ export interface ExternalServiceApi {
export interface CreateExternalServiceBasicArgs {
api: ExternalServiceApi;
- createExternalService: (credentials: ExternalServiceCredentials) => ExternalService;
+ createExternalService: (
+ credentials: ExternalServiceCredentials,
+ logger: Logger,
+ proxySettings?: any
+ ) => ExternalService;
+ logger: Logger;
}
export interface CreateExternalServiceArgs extends CreateExternalServiceBasicArgs {
diff --git a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts
index 82dedb09c429e..d895bf386a367 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts
@@ -67,6 +67,7 @@ export const mapParams = (
export const createConnectorExecutor = ({
api,
createExternalService,
+ logger,
}: CreateExternalServiceBasicArgs) => async (
execOptions: ActionTypeExecutorOptions<
ExternalIncidentServiceConfiguration,
@@ -83,10 +84,14 @@ export const createConnectorExecutor = ({
actionId,
};
- const externalService = createExternalService({
- config,
- secrets,
- });
+ const externalService = createExternalService(
+ {
+ config,
+ secrets,
+ },
+ logger,
+ execOptions.proxySettings
+ );
if (!api[subAction]) {
throw new Error('[Action][ExternalService] Unsupported subAction type.');
@@ -122,10 +127,11 @@ export const createConnector = ({
validate,
createExternalService,
validationSchema,
+ logger,
}: CreateExternalServiceArgs) => {
return ({
configurationUtilities,
- executor = createConnectorExecutor({ api, createExternalService }),
+ executor = createConnectorExecutor({ api, createExternalService, logger }),
}: CreateActionTypeArgs): ActionType => ({
...config,
validate: {
diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts
index 195f6db538ae5..62f369816d714 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts
@@ -269,6 +269,7 @@ describe('execute()', () => {
"message": "a message to you",
"subject": "the subject",
},
+ "proxySettings": undefined,
"routing": Object {
"bcc": Array [
"jimmy@example.com",
@@ -326,6 +327,7 @@ describe('execute()', () => {
"message": "a message to you",
"subject": "the subject",
},
+ "proxySettings": undefined,
"routing": Object {
"bcc": Array [
"jimmy@example.com",
diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.ts b/x-pack/plugins/actions/server/builtin_action_types/email.ts
index a51a0432a01e0..e9dc4eea5dcfc 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/email.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/email.ts
@@ -184,6 +184,7 @@ async function executor(
subject: params.subject,
message: params.message,
},
+ proxySettings: execOptions.proxySettings,
};
let result;
diff --git a/x-pack/plugins/actions/server/builtin_action_types/index.ts b/x-pack/plugins/actions/server/builtin_action_types/index.ts
index 80a171cbe624d..3591e05fb3acf 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/index.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/index.ts
@@ -31,9 +31,9 @@ export function registerBuiltInActionTypes({
actionTypeRegistry.register(getIndexActionType({ logger }));
actionTypeRegistry.register(getPagerDutyActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getServerLogActionType({ logger }));
- actionTypeRegistry.register(getSlackActionType({ configurationUtilities }));
+ actionTypeRegistry.register(getSlackActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getServiceNowActionType({ logger, configurationUtilities }));
- actionTypeRegistry.register(getJiraActionType({ configurationUtilities }));
- actionTypeRegistry.register(getResilientActionType({ configurationUtilities }));
+ actionTypeRegistry.register(getJiraActionType({ logger, configurationUtilities }));
+ actionTypeRegistry.register(getResilientActionType({ logger, configurationUtilities }));
}
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts
index a2d7bb5930a75..66be0bad02d7b 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts
@@ -4,21 +4,33 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { Logger } from '../../../../../../src/core/server';
import { createConnector } from '../case/utils';
+import { ActionType } from '../../types';
import { api } from './api';
import { config } from './config';
import { validate } from './validators';
import { createExternalService } from './service';
import { JiraSecretConfiguration, JiraPublicConfiguration } from './schema';
+import { ActionsConfigurationUtilities } from '../../actions_config';
-export const getActionType = createConnector({
- api,
- config,
- validate,
- createExternalService,
- validationSchema: {
- config: JiraPublicConfiguration,
- secrets: JiraSecretConfiguration,
- },
-});
+export function getActionType({
+ logger,
+ configurationUtilities,
+}: {
+ logger: Logger;
+ configurationUtilities: ActionsConfigurationUtilities;
+}): ActionType {
+ return createConnector({
+ api,
+ config,
+ validate,
+ createExternalService,
+ validationSchema: {
+ config: JiraPublicConfiguration,
+ secrets: JiraSecretConfiguration,
+ },
+ logger,
+ })({ configurationUtilities });
+}
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts
index 3de3926b7d821..547595b4c183f 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts
@@ -9,6 +9,9 @@ import axios from 'axios';
import { createExternalService } from './service';
import * as utils from '../lib/axios_utils';
import { ExternalService } from '../case/types';
+import { Logger } from '../../../../../../src/core/server';
+import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
+const logger = loggingSystemMock.create().get() as jest.Mocked;
jest.mock('axios');
jest.mock('../lib/axios_utils', () => {
@@ -26,10 +29,13 @@ describe('Jira service', () => {
let service: ExternalService;
beforeAll(() => {
- service = createExternalService({
- config: { apiUrl: 'https://siem-kibana.atlassian.net', projectKey: 'CK' },
- secrets: { apiToken: 'token', email: 'elastic@elastic.com' },
- });
+ service = createExternalService(
+ {
+ config: { apiUrl: 'https://siem-kibana.atlassian.net', projectKey: 'CK' },
+ secrets: { apiToken: 'token', email: 'elastic@elastic.com' },
+ },
+ logger
+ );
});
beforeEach(() => {
@@ -39,37 +45,49 @@ describe('Jira service', () => {
describe('createExternalService', () => {
test('throws without url', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: null, projectKey: 'CK' },
- secrets: { apiToken: 'token', email: 'elastic@elastic.com' },
- })
+ createExternalService(
+ {
+ config: { apiUrl: null, projectKey: 'CK' },
+ secrets: { apiToken: 'token', email: 'elastic@elastic.com' },
+ },
+ logger
+ )
).toThrow();
});
test('throws without projectKey', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: 'test.com', projectKey: null },
- secrets: { apiToken: 'token', email: 'elastic@elastic.com' },
- })
+ createExternalService(
+ {
+ config: { apiUrl: 'test.com', projectKey: null },
+ secrets: { apiToken: 'token', email: 'elastic@elastic.com' },
+ },
+ logger
+ )
).toThrow();
});
test('throws without username', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: 'test.com' },
- secrets: { apiToken: '', email: 'elastic@elastic.com' },
- })
+ createExternalService(
+ {
+ config: { apiUrl: 'test.com' },
+ secrets: { apiToken: '', email: 'elastic@elastic.com' },
+ },
+ logger
+ )
).toThrow();
});
test('throws without password', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: 'test.com' },
- secrets: { apiToken: '', email: undefined },
- })
+ createExternalService(
+ {
+ config: { apiUrl: 'test.com' },
+ secrets: { apiToken: '', email: undefined },
+ },
+ logger
+ )
).toThrow();
});
});
@@ -92,6 +110,7 @@ describe('Jira service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1',
+ logger,
});
});
@@ -146,6 +165,7 @@ describe('Jira service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue',
+ logger,
method: 'post',
data: {
fields: {
@@ -210,6 +230,7 @@ describe('Jira service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
+ logger,
method: 'put',
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1',
data: { fields: { summary: 'title', description: 'desc' } },
@@ -272,6 +293,7 @@ describe('Jira service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
+ logger,
method: 'post',
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1/comment',
data: { body: 'comment' },
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts
index 240b645c3a7dc..aec73cfb375ed 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts
@@ -7,6 +7,7 @@
import axios from 'axios';
import { ExternalServiceCredentials, ExternalService, ExternalServiceParams } from '../case/types';
+import { Logger } from '../../../../../../src/core/server';
import {
JiraPublicConfigurationType,
JiraSecretConfigurationType,
@@ -17,6 +18,7 @@ import {
import * as i18n from './translations';
import { request, getErrorMessage } from '../lib/axios_utils';
+import { ProxySettings } from '../../types';
const VERSION = '2';
const BASE_URL = `rest/api/${VERSION}`;
@@ -25,10 +27,11 @@ const COMMENT_URL = `comment`;
const VIEW_INCIDENT_URL = `browse`;
-export const createExternalService = ({
- config,
- secrets,
-}: ExternalServiceCredentials): ExternalService => {
+export const createExternalService = (
+ { config, secrets }: ExternalServiceCredentials,
+ logger: Logger,
+ proxySettings?: ProxySettings
+): ExternalService => {
const { apiUrl: url, projectKey } = config as JiraPublicConfigurationType;
const { apiToken, email } = secrets as JiraSecretConfigurationType;
@@ -55,6 +58,8 @@ export const createExternalService = ({
const res = await request({
axios: axiosInstance,
url: `${incidentUrl}/${id}`,
+ logger,
+ proxySettings,
});
const { fields, ...rest } = res.data;
@@ -75,10 +80,12 @@ export const createExternalService = ({
const res = await request({
axios: axiosInstance,
url: `${incidentUrl}`,
+ logger,
method: 'post',
data: {
fields: { ...incident, project: { key: projectKey }, issuetype: { name: 'Task' } },
},
+ proxySettings,
});
const updatedIncident = await getIncident(res.data.id);
@@ -102,7 +109,9 @@ export const createExternalService = ({
axios: axiosInstance,
method: 'put',
url: `${incidentUrl}/${incidentId}`,
+ logger,
data: { fields: { ...incident } },
+ proxySettings,
});
const updatedIncident = await getIncident(incidentId);
@@ -129,7 +138,9 @@ export const createExternalService = ({
axios: axiosInstance,
method: 'post',
url: getCommentsURL(incidentId),
+ logger,
data: { body: comment.comment },
+ proxySettings,
});
return {
diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts
index 4a52ae60bcdda..844aa6d2de7ed 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts
@@ -5,7 +5,11 @@
*/
import axios from 'axios';
-import { addTimeZoneToDate, throwIfNotAlive, request, patch, getErrorMessage } from './axios_utils';
+import HttpProxyAgent from 'http-proxy-agent';
+import { Logger } from '../../../../../../src/core/server';
+import { addTimeZoneToDate, request, patch, getErrorMessage } from './axios_utils';
+import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
+const logger = loggingSystemMock.create().get() as jest.Mocked;
jest.mock('axios');
const axiosMock = (axios as unknown) as jest.Mock;
@@ -21,26 +25,6 @@ describe('addTimeZoneToDate', () => {
});
});
-describe('throwIfNotAlive ', () => {
- test('throws correctly when status is invalid', async () => {
- expect(() => {
- throwIfNotAlive(404, 'application/json');
- }).toThrow('Instance is not alive.');
- });
-
- test('throws correctly when content is invalid', () => {
- expect(() => {
- throwIfNotAlive(200, 'application/html');
- }).toThrow('Instance is not alive.');
- });
-
- test('do NOT throws with custom validStatusCodes', async () => {
- expect(() => {
- throwIfNotAlive(404, 'application/json', [404]);
- }).not.toThrow('Instance is not alive.');
- });
-});
-
describe('request', () => {
beforeEach(() => {
axiosMock.mockImplementation(() => ({
@@ -51,9 +35,22 @@ describe('request', () => {
});
test('it fetch correctly with defaults', async () => {
- const res = await request({ axios, url: '/test' });
+ const res = await request({
+ axios,
+ url: '/test',
+ logger,
+ });
- expect(axiosMock).toHaveBeenCalledWith('/test', { method: 'get', data: {} });
+ expect(axiosMock).toHaveBeenCalledWith('/test', {
+ method: 'get',
+ data: {},
+ headers: undefined,
+ httpAgent: undefined,
+ httpsAgent: undefined,
+ params: undefined,
+ proxy: false,
+ validateStatus: undefined,
+ });
expect(res).toEqual({
status: 200,
headers: { 'content-type': 'application/json' },
@@ -61,10 +58,27 @@ describe('request', () => {
});
});
- test('it fetch correctly', async () => {
- const res = await request({ axios, url: '/test', method: 'post', data: { id: '123' } });
+ test('it have been called with proper proxy agent', async () => {
+ const res = await request({
+ axios,
+ url: '/testProxy',
+ logger,
+ proxySettings: {
+ proxyUrl: 'http://localhost:1212',
+ rejectUnauthorizedCertificates: false,
+ },
+ });
- expect(axiosMock).toHaveBeenCalledWith('/test', { method: 'post', data: { id: '123' } });
+ expect(axiosMock).toHaveBeenCalledWith('/testProxy', {
+ method: 'get',
+ data: {},
+ headers: undefined,
+ httpAgent: new HttpProxyAgent('http://localhost:1212'),
+ httpsAgent: new HttpProxyAgent('http://localhost:1212'),
+ params: undefined,
+ proxy: false,
+ validateStatus: undefined,
+ });
expect(res).toEqual({
status: 200,
headers: { 'content-type': 'application/json' },
@@ -72,14 +86,24 @@ describe('request', () => {
});
});
- test('it throws correctly', async () => {
- axiosMock.mockImplementation(() => ({
- status: 404,
+ test('it fetch correctly', async () => {
+ const res = await request({ axios, url: '/test', method: 'post', logger, data: { id: '123' } });
+
+ expect(axiosMock).toHaveBeenCalledWith('/test', {
+ method: 'post',
+ data: { id: '123' },
+ headers: undefined,
+ httpAgent: undefined,
+ httpsAgent: undefined,
+ params: undefined,
+ proxy: false,
+ validateStatus: undefined,
+ });
+ expect(res).toEqual({
+ status: 200,
headers: { 'content-type': 'application/json' },
data: { incidentId: '123' },
- }));
-
- await expect(request({ axios, url: '/test' })).rejects.toThrow();
+ });
});
});
@@ -92,8 +116,17 @@ describe('patch', () => {
});
test('it fetch correctly', async () => {
- await patch({ axios, url: '/test', data: { id: '123' } });
- expect(axiosMock).toHaveBeenCalledWith('/test', { method: 'patch', data: { id: '123' } });
+ await patch({ axios, url: '/test', data: { id: '123' }, logger });
+ expect(axiosMock).toHaveBeenCalledWith('/test', {
+ method: 'patch',
+ data: { id: '123' },
+ headers: undefined,
+ httpAgent: undefined,
+ httpsAgent: undefined,
+ params: undefined,
+ proxy: false,
+ validateStatus: undefined,
+ });
});
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts
index d527cf632bace..e26a3b686179c 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts
@@ -4,50 +4,68 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { AxiosInstance, Method, AxiosResponse } from 'axios';
-
-export const throwIfNotAlive = (
- status: number,
- contentType: string,
- validStatusCodes: number[] = [200, 201, 204]
-) => {
- if (!validStatusCodes.includes(status) || !contentType.includes('application/json')) {
- throw new Error('Instance is not alive.');
- }
-};
+import { AxiosInstance, Method, AxiosResponse, AxiosBasicCredentials } from 'axios';
+import { Logger } from '../../../../../../src/core/server';
+import { ProxySettings } from '../../types';
+import { getProxyAgent } from './get_proxy_agent';
export const request = async ({
axios,
url,
+ logger,
method = 'get',
data,
params,
+ proxySettings,
+ headers,
+ validateStatus,
+ auth,
}: {
axios: AxiosInstance;
url: string;
+ logger: Logger;
method?: Method;
data?: T;
params?: unknown;
+ proxySettings?: ProxySettings;
+ headers?: Record | null;
+ validateStatus?: (status: number) => boolean;
+ auth?: AxiosBasicCredentials;
}): Promise => {
- const res = await axios(url, { method, data: data ?? {}, params });
- throwIfNotAlive(res.status, res.headers['content-type']);
- return res;
+ return await axios(url, {
+ method,
+ data: data ?? {},
+ params,
+ auth,
+ // use httpsAgent and embedded proxy: false, to be able to handle fail on invalid certs
+ httpsAgent: proxySettings ? getProxyAgent(proxySettings, logger) : undefined,
+ httpAgent: proxySettings ? getProxyAgent(proxySettings, logger) : undefined,
+ proxy: false, // the same way as it done for IncomingWebhook in
+ headers,
+ validateStatus,
+ });
};
export const patch = async ({
axios,
url,
data,
+ logger,
+ proxySettings,
}: {
axios: AxiosInstance;
url: string;
data: T;
+ logger: Logger;
+ proxySettings?: ProxySettings;
}): Promise => {
return request({
axios,
url,
+ logger,
method: 'patch',
data,
+ proxySettings,
});
};
diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts
new file mode 100644
index 0000000000000..2468fab8c6ac5
--- /dev/null
+++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import HttpProxyAgent from 'http-proxy-agent';
+import { HttpsProxyAgent } from 'https-proxy-agent';
+import { Logger } from '../../../../../../src/core/server';
+import { getProxyAgent } from './get_proxy_agent';
+import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
+const logger = loggingSystemMock.create().get() as jest.Mocked;
+
+describe('getProxyAgent', () => {
+ test('return HttpsProxyAgent for https proxy url', () => {
+ const agent = getProxyAgent(
+ { proxyUrl: 'https://someproxyhost', rejectUnauthorizedCertificates: false },
+ logger
+ );
+ expect(agent instanceof HttpsProxyAgent).toBeTruthy();
+ });
+
+ test('return HttpProxyAgent for http proxy url', () => {
+ const agent = getProxyAgent(
+ { proxyUrl: 'http://someproxyhost', rejectUnauthorizedCertificates: false },
+ logger
+ );
+ expect(agent instanceof HttpProxyAgent).toBeTruthy();
+ });
+});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts
new file mode 100644
index 0000000000000..bb4dadd3a4698
--- /dev/null
+++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import HttpProxyAgent from 'http-proxy-agent';
+import { HttpsProxyAgent } from 'https-proxy-agent';
+import { Logger } from '../../../../../../src/core/server';
+import { ProxySettings } from '../../types';
+
+export function getProxyAgent(
+ proxySettings: ProxySettings,
+ logger: Logger
+): HttpsProxyAgent | HttpProxyAgent {
+ logger.debug(`Create proxy agent for ${proxySettings.proxyUrl}.`);
+
+ if (/^https/i.test(proxySettings.proxyUrl)) {
+ const proxyUrl = new URL(proxySettings.proxyUrl);
+ return new HttpsProxyAgent({
+ host: proxyUrl.hostname,
+ port: Number(proxyUrl.port),
+ protocol: proxyUrl.protocol,
+ headers: proxySettings.proxyHeaders,
+ // do not fail on invalid certs if value is false
+ rejectUnauthorized: proxySettings.rejectUnauthorizedCertificates,
+ });
+ } else {
+ return new HttpProxyAgent(proxySettings.proxyUrl);
+ }
+}
diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts
index 92f88ebe0be22..d78237beb98a1 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts
@@ -5,22 +5,34 @@
*/
import axios, { AxiosResponse } from 'axios';
-import { Services } from '../../types';
+import { Logger } from '../../../../../../src/core/server';
+import { Services, ProxySettings } from '../../types';
+import { request } from './axios_utils';
interface PostPagerdutyOptions {
apiUrl: string;
data: unknown;
headers: Record;
services: Services;
+ proxySettings?: ProxySettings;
}
// post an event to pagerduty
-export async function postPagerduty(options: PostPagerdutyOptions): Promise {
- const { apiUrl, data, headers } = options;
- const axiosOptions = {
+export async function postPagerduty(
+ options: PostPagerdutyOptions,
+ logger: Logger
+): Promise {
+ const { apiUrl, data, headers, proxySettings } = options;
+ const axiosInstance = axios.create();
+
+ return await request({
+ axios: axiosInstance,
+ url: apiUrl,
+ method: 'post',
+ logger,
+ data,
+ proxySettings,
headers,
validateStatus: () => true,
- };
-
- return axios.post(apiUrl, data, axiosOptions);
+ });
}
diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts
index 3514bd4257b0f..8287ee944bca9 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts
@@ -12,6 +12,7 @@ import { Logger } from '../../../../../../src/core/server';
import { sendEmail } from './send_email';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
import nodemailer from 'nodemailer';
+import { ProxySettings } from '../../types';
const createTransportMock = nodemailer.createTransport as jest.Mock;
const sendMailMockResult = { result: 'does not matter' };
@@ -63,6 +64,59 @@ describe('send_email module', () => {
});
test('handles unauthenticated email using not secure host/port', async () => {
+ const sendEmailOptions = getSendEmailOptions(
+ {
+ transport: {
+ host: 'example.com',
+ port: 1025,
+ },
+ },
+ {
+ proxyUrl: 'https://example.com',
+ rejectUnauthorizedCertificates: false,
+ }
+ );
+ delete sendEmailOptions.transport.service;
+ delete sendEmailOptions.transport.user;
+ delete sendEmailOptions.transport.password;
+ const result = await sendEmail(mockLogger, sendEmailOptions);
+ expect(result).toBe(sendMailMockResult);
+ expect(createTransportMock.mock.calls[0]).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "headers": undefined,
+ "host": "example.com",
+ "port": 1025,
+ "proxy": "https://example.com",
+ "secure": false,
+ "tls": Object {
+ "rejectUnauthorized": false,
+ },
+ },
+ ]
+ `);
+ expect(sendMailMock.mock.calls[0]).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "bcc": Array [],
+ "cc": Array [
+ "bob@example.com",
+ "robert@example.com",
+ ],
+ "from": "fred@example.com",
+ "html": "a message
+ ",
+ "subject": "a subject",
+ "text": "a message",
+ "to": Array [
+ "jim@example.com",
+ ],
+ },
+ ]
+ `);
+ });
+
+ test('rejectUnauthorized default setting email using not secure host/port', async () => {
const sendEmailOptions = getSendEmailOptions({
transport: {
host: 'example.com',
@@ -80,9 +134,6 @@ describe('send_email module', () => {
"host": "example.com",
"port": 1025,
"secure": false,
- "tls": Object {
- "rejectUnauthorized": false,
- },
},
]
`);
@@ -161,7 +212,10 @@ describe('send_email module', () => {
});
});
-function getSendEmailOptions({ content = {}, routing = {}, transport = {} } = {}) {
+function getSendEmailOptions(
+ { content = {}, routing = {}, transport = {} } = {},
+ proxySettings?: ProxySettings
+) {
return {
content: {
...content,
@@ -181,5 +235,6 @@ function getSendEmailOptions({ content = {}, routing = {}, transport = {} } = {}
user: 'elastic',
password: 'changeme',
},
+ proxySettings,
};
}
diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts
index 869db34f034ae..a4f32f1880cb5 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts
@@ -6,10 +6,10 @@
// info on nodemailer: https://nodemailer.com/about/
import nodemailer from 'nodemailer';
-
import { default as MarkdownIt } from 'markdown-it';
import { Logger } from '../../../../../../src/core/server';
+import { ProxySettings } from '../../types';
// an email "service" which doesn't actually send, just returns what it would send
export const JSON_TRANSPORT_SERVICE = '__json';
@@ -18,6 +18,7 @@ export interface SendEmailOptions {
transport: Transport;
routing: Routing;
content: Content;
+ proxySettings?: ProxySettings;
}
// config validation ensures either service is set or host/port are set
@@ -44,7 +45,7 @@ export interface Content {
// send an email
export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise {
- const { transport, routing, content } = options;
+ const { transport, routing, content, proxySettings } = options;
const { service, host, port, secure, user, password } = transport;
const { from, to, cc, bcc } = routing;
const { subject, message } = content;
@@ -67,11 +68,16 @@ export async function sendEmail(logger: Logger, options: SendEmailOptions): Prom
transportConfig.host = host;
transportConfig.port = port;
transportConfig.secure = !!secure;
- if (!transportConfig.secure) {
+ if (proxySettings && !transportConfig.secure) {
transportConfig.tls = {
- rejectUnauthorized: false,
+ // do not fail on invalid certs if value is false
+ rejectUnauthorized: proxySettings?.rejectUnauthorizedCertificates,
};
}
+ if (proxySettings) {
+ transportConfig.proxy = proxySettings.proxyUrl;
+ transportConfig.headers = proxySettings.proxyHeaders;
+ }
}
const nodemailerTransport = nodemailer.createTransport(transportConfig);
diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts
index b76e57419bc56..c0edfc530e738 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts
@@ -161,6 +161,7 @@ async function executor(
const secrets = execOptions.secrets;
const params = execOptions.params;
const services = execOptions.services;
+ const proxySettings = execOptions.proxySettings;
const apiUrl = getPagerDutyApiUrl(config);
const headers = {
@@ -171,7 +172,7 @@ async function executor(
let response;
try {
- response = await postPagerduty({ apiUrl, data, headers, services });
+ response = await postPagerduty({ apiUrl, data, headers, services, proxySettings }, logger);
} catch (err) {
const message = i18n.translate('xpack.actions.builtin.pagerduty.postingErrorMessage', {
defaultMessage: 'error posting pagerduty event',
diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts
index e98bc71559d3f..1e9cb15589702 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { Logger } from '../../../../../../src/core/server';
import { createConnector } from '../case/utils';
import { api } from './api';
@@ -11,14 +12,25 @@ import { config } from './config';
import { validate } from './validators';
import { createExternalService } from './service';
import { ResilientSecretConfiguration, ResilientPublicConfiguration } from './schema';
+import { ActionsConfigurationUtilities } from '../../actions_config';
+import { ActionType } from '../../types';
-export const getActionType = createConnector({
- api,
- config,
- validate,
- createExternalService,
- validationSchema: {
- config: ResilientPublicConfiguration,
- secrets: ResilientSecretConfiguration,
- },
-});
+export function getActionType({
+ logger,
+ configurationUtilities,
+}: {
+ logger: Logger;
+ configurationUtilities: ActionsConfigurationUtilities;
+}): ActionType {
+ return createConnector({
+ api,
+ config,
+ validate,
+ createExternalService,
+ validationSchema: {
+ config: ResilientPublicConfiguration,
+ secrets: ResilientSecretConfiguration,
+ },
+ logger,
+ })({ configurationUtilities });
+}
diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts
index 573885698014e..a9271671f68b9 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts
@@ -9,6 +9,9 @@ import axios from 'axios';
import { createExternalService, getValueTextContent, formatUpdateRequest } from './service';
import * as utils from '../lib/axios_utils';
import { ExternalService } from '../case/types';
+import { Logger } from '../../../../../../src/core/server';
+import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
+const logger = loggingSystemMock.create().get() as jest.Mocked;
jest.mock('axios');
jest.mock('../lib/axios_utils', () => {
@@ -72,10 +75,13 @@ describe('IBM Resilient service', () => {
let service: ExternalService;
beforeAll(() => {
- service = createExternalService({
- config: { apiUrl: 'https://resilient.elastic.co', orgId: '201' },
- secrets: { apiKeyId: 'keyId', apiKeySecret: 'secret' },
- });
+ service = createExternalService(
+ {
+ config: { apiUrl: 'https://resilient.elastic.co', orgId: '201' },
+ secrets: { apiKeyId: 'keyId', apiKeySecret: 'secret' },
+ },
+ logger
+ );
});
afterAll(() => {
@@ -138,37 +144,49 @@ describe('IBM Resilient service', () => {
describe('createExternalService', () => {
test('throws without url', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: null, orgId: '201' },
- secrets: { apiKeyId: 'token', apiKeySecret: 'secret' },
- })
+ createExternalService(
+ {
+ config: { apiUrl: null, orgId: '201' },
+ secrets: { apiKeyId: 'token', apiKeySecret: 'secret' },
+ },
+ logger
+ )
).toThrow();
});
test('throws without orgId', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: 'test.com', orgId: null },
- secrets: { apiKeyId: 'token', apiKeySecret: 'secret' },
- })
+ createExternalService(
+ {
+ config: { apiUrl: 'test.com', orgId: null },
+ secrets: { apiKeyId: 'token', apiKeySecret: 'secret' },
+ },
+ logger
+ )
).toThrow();
});
test('throws without username', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: 'test.com', orgId: '201' },
- secrets: { apiKeyId: '', apiKeySecret: 'secret' },
- })
+ createExternalService(
+ {
+ config: { apiUrl: 'test.com', orgId: '201' },
+ secrets: { apiKeyId: '', apiKeySecret: 'secret' },
+ },
+ logger
+ )
).toThrow();
});
test('throws without password', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: 'test.com', orgId: '201' },
- secrets: { apiKeyId: '', apiKeySecret: undefined },
- })
+ createExternalService(
+ {
+ config: { apiUrl: 'test.com', orgId: '201' },
+ secrets: { apiKeyId: '', apiKeySecret: undefined },
+ },
+ logger
+ )
).toThrow();
});
});
@@ -197,6 +215,7 @@ describe('IBM Resilient service', () => {
await service.getIncident('1');
expect(requestMock).toHaveBeenCalledWith({
axios,
+ logger,
url: 'https://resilient.elastic.co/rest/orgs/201/incidents/1',
params: {
text_content_output_format: 'objects_convert',
@@ -256,6 +275,7 @@ describe('IBM Resilient service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
url: 'https://resilient.elastic.co/rest/orgs/201/incidents',
+ logger,
method: 'post',
data: {
name: 'title',
@@ -311,6 +331,7 @@ describe('IBM Resilient service', () => {
// The second call to the API is the update call.
expect(requestMock.mock.calls[1][0]).toEqual({
axios,
+ logger,
method: 'patch',
url: 'https://resilient.elastic.co/rest/orgs/201/incidents/1',
data: {
@@ -392,7 +413,9 @@ describe('IBM Resilient service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
+ logger,
method: 'post',
+ proxySettings: undefined,
url: 'https://resilient.elastic.co/rest/orgs/201/incidents/1/comments',
data: {
text: {
diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts
index 8d0526ca3b571..b2150081f2c89 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts
@@ -6,6 +6,7 @@
import axios from 'axios';
+import { Logger } from '../../../../../../src/core/server';
import { ExternalServiceCredentials, ExternalService, ExternalServiceParams } from '../case/types';
import {
ResilientPublicConfigurationType,
@@ -19,6 +20,7 @@ import {
import * as i18n from './translations';
import { getErrorMessage, request } from '../lib/axios_utils';
+import { ProxySettings } from '../../types';
const BASE_URL = `rest`;
const INCIDENT_URL = `incidents`;
@@ -57,10 +59,11 @@ export const formatUpdateRequest = ({
};
};
-export const createExternalService = ({
- config,
- secrets,
-}: ExternalServiceCredentials): ExternalService => {
+export const createExternalService = (
+ { config, secrets }: ExternalServiceCredentials,
+ logger: Logger,
+ proxySettings?: ProxySettings
+): ExternalService => {
const { apiUrl: url, orgId } = config as ResilientPublicConfigurationType;
const { apiKeyId, apiKeySecret } = secrets as ResilientSecretConfigurationType;
@@ -88,9 +91,11 @@ export const createExternalService = ({
const res = await request({
axios: axiosInstance,
url: `${incidentUrl}/${id}`,
+ logger,
params: {
text_content_output_format: 'objects_convert',
},
+ proxySettings,
});
return { ...res.data, description: res.data.description?.content ?? '' };
@@ -107,6 +112,7 @@ export const createExternalService = ({
axios: axiosInstance,
url: `${incidentUrl}`,
method: 'post',
+ logger,
data: {
...incident,
description: {
@@ -115,6 +121,7 @@ export const createExternalService = ({
},
discovered_date: Date.now(),
},
+ proxySettings,
});
return {
@@ -139,7 +146,9 @@ export const createExternalService = ({
axios: axiosInstance,
method: 'patch',
url: `${incidentUrl}/${incidentId}`,
+ logger,
data,
+ proxySettings,
});
if (!res.data.success) {
@@ -170,7 +179,9 @@ export const createExternalService = ({
axios: axiosInstance,
method: 'post',
url: getCommentsURL(incidentId),
+ logger,
data: { text: { format: 'text', content: comment.comment } },
+ proxySettings,
});
return {
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts
index 109008b8fc9fb..3addbe7c54dac 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts
@@ -76,10 +76,14 @@ async function executor(
const { subAction, subActionParams } = params;
let data: PushToServiceResponse | null = null;
- const externalService = createExternalService({
- config,
- secrets,
- });
+ const externalService = createExternalService(
+ {
+ config,
+ secrets,
+ },
+ logger,
+ execOptions.proxySettings
+ );
if (!api[subAction]) {
const errorMessage = `[Action][ExternalService] Unsupported subAction type ${subAction}.`;
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts
index 07d60ec9f7a05..2adcdf561ce17 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts
@@ -9,6 +9,9 @@ import axios from 'axios';
import { createExternalService } from './service';
import * as utils from '../lib/axios_utils';
import { ExternalService } from './types';
+import { Logger } from '../../../../../../src/core/server';
+import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
+const logger = loggingSystemMock.create().get() as jest.Mocked;
jest.mock('axios');
jest.mock('../lib/axios_utils', () => {
@@ -28,10 +31,13 @@ describe('ServiceNow service', () => {
let service: ExternalService;
beforeAll(() => {
- service = createExternalService({
- config: { apiUrl: 'https://dev102283.service-now.com' },
- secrets: { username: 'admin', password: 'admin' },
- });
+ service = createExternalService(
+ {
+ config: { apiUrl: 'https://dev102283.service-now.com' },
+ secrets: { username: 'admin', password: 'admin' },
+ },
+ logger
+ );
});
beforeEach(() => {
@@ -41,28 +47,37 @@ describe('ServiceNow service', () => {
describe('createExternalService', () => {
test('throws without url', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: null },
- secrets: { username: 'admin', password: 'admin' },
- })
+ createExternalService(
+ {
+ config: { apiUrl: null },
+ secrets: { username: 'admin', password: 'admin' },
+ },
+ logger
+ )
).toThrow();
});
test('throws without username', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: 'test.com' },
- secrets: { username: '', password: 'admin' },
- })
+ createExternalService(
+ {
+ config: { apiUrl: 'test.com' },
+ secrets: { username: '', password: 'admin' },
+ },
+ logger
+ )
).toThrow();
});
test('throws without password', () => {
expect(() =>
- createExternalService({
- config: { apiUrl: 'test.com' },
- secrets: { username: '', password: undefined },
- })
+ createExternalService(
+ {
+ config: { apiUrl: 'test.com' },
+ secrets: { username: '', password: undefined },
+ },
+ logger
+ )
).toThrow();
});
});
@@ -84,6 +99,7 @@ describe('ServiceNow service', () => {
await service.getIncident('1');
expect(requestMock).toHaveBeenCalledWith({
axios,
+ logger,
url: 'https://dev102283.service-now.com/api/now/v2/table/incident/1',
});
});
@@ -127,6 +143,7 @@ describe('ServiceNow service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
+ logger,
url: 'https://dev102283.service-now.com/api/now/v2/table/incident',
method: 'post',
data: { short_description: 'title', description: 'desc' },
@@ -179,6 +196,7 @@ describe('ServiceNow service', () => {
expect(patchMock).toHaveBeenCalledWith({
axios,
+ logger,
url: 'https://dev102283.service-now.com/api/now/v2/table/incident/1',
data: { short_description: 'title', description: 'desc' },
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts
index 2b5204af2eb7d..cf1c26e6462a2 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts
@@ -9,8 +9,10 @@ import axios from 'axios';
import { ExternalServiceCredentials, ExternalService, ExternalServiceParams } from './types';
import * as i18n from './translations';
+import { Logger } from '../../../../../../src/core/server';
import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType } from './types';
import { request, getErrorMessage, addTimeZoneToDate, patch } from '../lib/axios_utils';
+import { ProxySettings } from '../../types';
const API_VERSION = 'v2';
const INCIDENT_URL = `api/now/${API_VERSION}/table/incident`;
@@ -18,10 +20,11 @@ const INCIDENT_URL = `api/now/${API_VERSION}/table/incident`;
// Based on: https://docs.servicenow.com/bundle/orlando-platform-user-interface/page/use/navigation/reference/r_NavigatingByURLExamples.html
const VIEW_INCIDENT_URL = `nav_to.do?uri=incident.do?sys_id=`;
-export const createExternalService = ({
- config,
- secrets,
-}: ExternalServiceCredentials): ExternalService => {
+export const createExternalService = (
+ { config, secrets }: ExternalServiceCredentials,
+ logger: Logger,
+ proxySettings?: ProxySettings
+): ExternalService => {
const { apiUrl: url } = config as ServiceNowPublicConfigurationType;
const { username, password } = secrets as ServiceNowSecretConfigurationType;
@@ -43,6 +46,8 @@ export const createExternalService = ({
const res = await request({
axios: axiosInstance,
url: `${incidentUrl}/${id}`,
+ logger,
+ proxySettings,
});
return { ...res.data.result };
@@ -58,6 +63,8 @@ export const createExternalService = ({
const res = await request({
axios: axiosInstance,
url: incidentUrl,
+ logger,
+ proxySettings,
params,
});
@@ -71,9 +78,13 @@ export const createExternalService = ({
const createIncident = async ({ incident }: ExternalServiceParams) => {
try {
+ logger.warn(`incident error : ${JSON.stringify(proxySettings)}`);
+ logger.warn(`incident error : ${url}`);
const res = await request({
axios: axiosInstance,
url: `${incidentUrl}`,
+ logger,
+ proxySettings,
method: 'post',
data: { ...(incident as Record) },
});
@@ -96,7 +107,9 @@ export const createExternalService = ({
const res = await patch({
axios: axiosInstance,
url: `${incidentUrl}/${incidentId}`,
+ logger,
data: { ...(incident as Record) },
+ proxySettings,
});
return {
diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts
index 6d4176067c3ba..812657138152c 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts
@@ -4,25 +4,40 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { Logger } from '../../../../../src/core/server';
import { Services, ActionTypeExecutorResult } from '../types';
import { validateParams, validateSecrets } from '../lib';
import { getActionType, SlackActionType, SlackActionTypeExecutorOptions } from './slack';
import { actionsConfigMock } from '../actions_config.mock';
import { actionsMock } from '../mocks';
+import { createActionTypeRegistry } from './index.test';
+
+jest.mock('@slack/webhook', () => {
+ return {
+ IncomingWebhook: jest.fn().mockImplementation(() => {
+ return { send: (message: string) => {} };
+ }),
+ };
+});
const ACTION_TYPE_ID = '.slack';
const services: Services = actionsMock.createServices();
let actionType: SlackActionType;
+let mockedLogger: jest.Mocked;
beforeAll(() => {
+ const { logger } = createActionTypeRegistry();
actionType = getActionType({
async executor(options) {
return { status: 'ok', actionId: options.actionId };
},
configurationUtilities: actionsConfigMock.create(),
+ logger,
});
+ mockedLogger = logger;
+ expect(actionType).toBeTruthy();
});
describe('action registeration', () => {
@@ -83,6 +98,7 @@ describe('validateActionTypeSecrets()', () => {
test('should validate and pass when the slack webhookUrl is whitelisted', () => {
actionType = getActionType({
+ logger: mockedLogger,
configurationUtilities: {
...actionsConfigMock.create(),
ensureWhitelistedUri: (url) => {
@@ -98,9 +114,10 @@ describe('validateActionTypeSecrets()', () => {
test('config validation returns an error if the specified URL isnt whitelisted', () => {
actionType = getActionType({
+ logger: mockedLogger,
configurationUtilities: {
...actionsConfigMock.create(),
- ensureWhitelistedHostname: (url) => {
+ ensureWhitelistedHostname: () => {
throw new Error(`target hostname is not whitelisted`);
},
},
@@ -136,6 +153,7 @@ describe('execute()', () => {
actionType = getActionType({
executor: mockSlackExecutor,
+ logger: mockedLogger,
configurationUtilities: actionsConfigMock.create(),
});
});
@@ -147,6 +165,10 @@ describe('execute()', () => {
config: {},
secrets: { webhookUrl: 'http://example.com' },
params: { message: 'this invocation should succeed' },
+ proxySettings: {
+ proxyUrl: 'https://someproxyhost',
+ rejectUnauthorizedCertificates: false,
+ },
});
expect(response).toMatchInlineSnapshot(`
Object {
@@ -170,4 +192,25 @@ describe('execute()', () => {
`"slack mockExecutor failure: this invocation should fail"`
);
});
+
+ test('calls the mock executor with success proxy', async () => {
+ const actionTypeProxy = getActionType({
+ logger: mockedLogger,
+ configurationUtilities: actionsConfigMock.create(),
+ });
+ await actionTypeProxy.executor({
+ actionId: 'some-id',
+ services,
+ config: {},
+ secrets: { webhookUrl: 'http://example.com' },
+ params: { message: 'this invocation should succeed' },
+ proxySettings: {
+ proxyUrl: 'https://someproxyhost',
+ rejectUnauthorizedCertificates: false,
+ },
+ });
+ expect(mockedLogger.info).toHaveBeenCalledWith(
+ 'IncomingWebhook was called with proxyUrl https://someproxyhost'
+ );
+ });
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts
index 209582585256b..293328c809435 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/slack.ts
@@ -6,11 +6,14 @@
import { URL } from 'url';
import { curry } from 'lodash';
+import { HttpsProxyAgent } from 'https-proxy-agent';
+import HttpProxyAgent from 'http-proxy-agent';
import { i18n } from '@kbn/i18n';
import { schema, TypeOf } from '@kbn/config-schema';
import { IncomingWebhook, IncomingWebhookResult } from '@slack/webhook';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, getOrElse } from 'fp-ts/lib/Option';
+import { Logger } from '../../../../../src/core/server';
import { getRetryAfterIntervalFromHeaders } from './lib/http_rersponse_retry_header';
import {
@@ -20,6 +23,7 @@ import {
ExecutorType,
} from '../types';
import { ActionsConfigurationUtilities } from '../actions_config';
+import { getProxyAgent } from './lib/get_proxy_agent';
export type SlackActionType = ActionType<{}, ActionTypeSecretsType, ActionParamsType, unknown>;
export type SlackActionTypeExecutorOptions = ActionTypeExecutorOptions<
@@ -49,9 +53,11 @@ const ParamsSchema = schema.object({
// customizing executor is only used for tests
export function getActionType({
+ logger,
configurationUtilities,
- executor = slackExecutor,
+ executor = curry(slackExecutor)({ logger }),
}: {
+ logger: Logger;
configurationUtilities: ActionsConfigurationUtilities;
executor?: ExecutorType<{}, ActionTypeSecretsType, ActionParamsType, unknown>;
}): SlackActionType {
@@ -99,6 +105,7 @@ function valdiateActionTypeConfig(
// action executor
async function slackExecutor(
+ { logger }: { logger: Logger },
execOptions: SlackActionTypeExecutorOptions
): Promise> {
const actionId = execOptions.actionId;
@@ -109,10 +116,22 @@ async function slackExecutor(
const { webhookUrl } = secrets;
const { message } = params;
+ let proxyAgent: HttpsProxyAgent | HttpProxyAgent | undefined;
+ if (execOptions.proxySettings) {
+ proxyAgent = getProxyAgent(execOptions.proxySettings, logger);
+ logger.info(`IncomingWebhook was called with proxyUrl ${execOptions.proxySettings.proxyUrl}`);
+ }
+
try {
- const webhook = new IncomingWebhook(webhookUrl);
+ // https://slack.dev/node-slack-sdk/webhook
+ // node-slack-sdk use Axios inside :)
+ const webhook = new IncomingWebhook(webhookUrl, {
+ agent: proxyAgent,
+ });
result = await webhook.send(message);
} catch (err) {
+ logger.error(`error on ${actionId} slack event: ${err.message}`);
+
if (err.original == null || err.original.response == null) {
return serviceErrorResult(actionId, err.message);
}
@@ -143,6 +162,8 @@ async function slackExecutor(
},
}
);
+ logger.error(`error on ${actionId} slack action: ${errMessage}`);
+
return errorResult(actionId, errMessage);
}
diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts
index 26dd8a1a1402a..ea9f30452918c 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts
@@ -4,10 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-jest.mock('axios', () => ({
- request: jest.fn(),
-}));
-
import { Services } from '../types';
import { validateConfig, validateSecrets, validateParams } from '../lib';
import { actionsConfigMock } from '../actions_config.mock';
@@ -24,7 +20,22 @@ import {
WebhookMethods,
} from './webhook';
-const axiosRequestMock = axios.request as jest.Mock;
+import * as utils from './lib/axios_utils';
+
+jest.mock('axios');
+jest.mock('./lib/axios_utils', () => {
+ const originalUtils = jest.requireActual('./lib/axios_utils');
+ return {
+ ...originalUtils,
+ request: jest.fn(),
+ patch: jest.fn(),
+ };
+});
+
+axios.create = jest.fn(() => axios);
+const requestMock = utils.request as jest.Mock;
+
+axios.create = jest.fn(() => axios);
const ACTION_TYPE_ID = '.webhook';
@@ -227,7 +238,7 @@ describe('params validation', () => {
describe('execute()', () => {
beforeAll(() => {
- axiosRequestMock.mockReset();
+ requestMock.mockReset();
actionType = getActionType({
logger: mockedLogger,
configurationUtilities: actionsConfigMock.create(),
@@ -235,8 +246,8 @@ describe('execute()', () => {
});
beforeEach(() => {
- axiosRequestMock.mockReset();
- axiosRequestMock.mockResolvedValue({
+ requestMock.mockReset();
+ requestMock.mockResolvedValue({
status: 200,
statusText: '',
data: '',
@@ -261,17 +272,42 @@ describe('execute()', () => {
params: { body: 'some data' },
});
- expect(axiosRequestMock.mock.calls[0][0]).toMatchInlineSnapshot(`
+ expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"auth": Object {
"password": "123",
"username": "abc",
},
+ "axios": undefined,
"data": "some data",
"headers": Object {
"aheader": "a value",
},
+ "logger": Object {
+ "context": Array [],
+ "debug": [MockFunction] {
+ "calls": Array [
+ Array [
+ "response from webhook action \\"some-id\\": [HTTP 200] ",
+ ],
+ ],
+ "results": Array [
+ Object {
+ "type": "return",
+ "value": undefined,
+ },
+ ],
+ },
+ "error": [MockFunction],
+ "fatal": [MockFunction],
+ "get": [MockFunction],
+ "info": [MockFunction],
+ "log": [MockFunction],
+ "trace": [MockFunction],
+ "warn": [MockFunction],
+ },
"method": "post",
+ "proxySettings": undefined,
"url": "https://abc.def/my-webhook",
}
`);
@@ -294,13 +330,38 @@ describe('execute()', () => {
params: { body: 'some data' },
});
- expect(axiosRequestMock.mock.calls[0][0]).toMatchInlineSnapshot(`
+ expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
+ "axios": undefined,
"data": "some data",
"headers": Object {
"aheader": "a value",
},
+ "logger": Object {
+ "context": Array [],
+ "debug": [MockFunction] {
+ "calls": Array [
+ Array [
+ "response from webhook action \\"some-id\\": [HTTP 200] ",
+ ],
+ ],
+ "results": Array [
+ Object {
+ "type": "return",
+ "value": undefined,
+ },
+ ],
+ },
+ "error": [MockFunction],
+ "fatal": [MockFunction],
+ "get": [MockFunction],
+ "info": [MockFunction],
+ "log": [MockFunction],
+ "trace": [MockFunction],
+ "warn": [MockFunction],
+ },
"method": "post",
+ "proxySettings": undefined,
"url": "https://abc.def/my-webhook",
}
`);
diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts
index be75742fa882e..d9a005565498d 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts
@@ -15,6 +15,7 @@ import { isOk, promiseResult, Result } from './lib/result_type';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
import { ActionsConfigurationUtilities } from '../actions_config';
import { Logger } from '../../../../../src/core/server';
+import { request } from './lib/axios_utils';
// config definition
export enum WebhookMethods {
@@ -136,13 +137,18 @@ export async function executor(
? { auth: { username: secrets.user, password: secrets.password } }
: {};
+ const axiosInstance = axios.create();
+
const result: Result = await promiseResult(
- axios.request({
+ request({
+ axios: axiosInstance,
method,
url,
+ logger,
...basicAuth,
headers,
data,
+ proxySettings: execOptions.proxySettings,
})
);
@@ -159,7 +165,7 @@ export async function executor(
if (error.response) {
const { status, statusText, headers: responseHeaders } = error.response;
const message = `[${status}] ${statusText}`;
- logger.warn(`error on ${actionId} webhook event: ${message}`);
+ logger.error(`error on ${actionId} webhook event: ${message}`);
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
// special handling for 5xx
@@ -178,7 +184,7 @@ export async function executor(
return errorResultInvalid(actionId, message);
}
- logger.warn(`error on ${actionId} webhook action: unexpected error`);
+ logger.error(`error on ${actionId} webhook action: unexpected error`);
return errorResultUnexpectedError(actionId);
}
}
diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts
index e86f2d7832828..795fbbf84145b 100644
--- a/x-pack/plugins/actions/server/config.test.ts
+++ b/x-pack/plugins/actions/server/config.test.ts
@@ -15,6 +15,7 @@ describe('config validation', () => {
"*",
],
"preconfigured": Object {},
+ "rejectUnauthorizedCertificates": true,
"whitelistedHosts": Array [
"*",
],
@@ -33,6 +34,7 @@ describe('config validation', () => {
},
},
},
+ rejectUnauthorizedCertificates: false,
};
expect(configSchema.validate(config)).toMatchInlineSnapshot(`
Object {
@@ -50,6 +52,7 @@ describe('config validation', () => {
"secrets": Object {},
},
},
+ "rejectUnauthorizedCertificates": false,
"whitelistedHosts": Array [
"*",
],
diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts
index b2f3fa2680a9c..ba80915ebe243 100644
--- a/x-pack/plugins/actions/server/config.ts
+++ b/x-pack/plugins/actions/server/config.ts
@@ -32,6 +32,9 @@ export const configSchema = schema.object({
defaultValue: {},
validate: validatePreconfigured,
}),
+ proxyUrl: schema.maybe(schema.string()),
+ proxyHeaders: schema.maybe(schema.recordOf(schema.string(), schema.string())),
+ rejectUnauthorizedCertificates: schema.boolean({ defaultValue: true }),
});
export type ActionsConfig = TypeOf;
diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts
index bce06c829b1bc..97c08124f5546 100644
--- a/x-pack/plugins/actions/server/lib/action_executor.ts
+++ b/x-pack/plugins/actions/server/lib/action_executor.ts
@@ -12,6 +12,7 @@ import {
GetServicesFunction,
RawAction,
PreConfiguredAction,
+ ProxySettings,
} from '../types';
import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server';
import { SpacesServiceSetup } from '../../../spaces/server';
@@ -28,6 +29,7 @@ export interface ActionExecutorContext {
actionTypeRegistry: ActionTypeRegistryContract;
eventLogger: IEventLogger;
preconfiguredActions: PreConfiguredAction[];
+ proxySettings?: ProxySettings;
}
export interface ExecuteOptions {
@@ -78,6 +80,7 @@ export class ActionExecutor {
eventLogger,
preconfiguredActions,
getActionsClientWithRequest,
+ proxySettings,
} = this.actionExecutorContext!;
const services = getServices(request);
@@ -133,6 +136,7 @@ export class ActionExecutor {
params: validatedParams,
config: validatedConfig,
secrets: validatedSecrets,
+ proxySettings,
});
} catch (err) {
rawResult = {
diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts
index ca93e88d01203..341a17889923f 100644
--- a/x-pack/plugins/actions/server/plugin.test.ts
+++ b/x-pack/plugins/actions/server/plugin.test.ts
@@ -34,6 +34,7 @@ describe('Actions Plugin', () => {
enabledActionTypes: ['*'],
whitelistedHosts: ['*'],
preconfigured: {},
+ rejectUnauthorizedCertificates: true,
});
plugin = new ActionsPlugin(context);
coreSetup = coreMock.createSetup();
@@ -194,6 +195,7 @@ describe('Actions Plugin', () => {
secrets: {},
},
},
+ rejectUnauthorizedCertificates: true,
});
plugin = new ActionsPlugin(context);
coreSetup = coreMock.createSetup();
@@ -217,7 +219,7 @@ describe('Actions Plugin', () => {
// coreMock.createSetup doesn't support Plugin generics
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await plugin.setup(coreSetup as any, pluginsSetup);
- const pluginStart = plugin.start(coreStart, pluginsStart);
+ const pluginStart = await plugin.start(coreStart, pluginsStart);
expect(pluginStart.isActionExecutable('preconfiguredServerLog', '.server-log')).toBe(true);
});
@@ -232,7 +234,7 @@ describe('Actions Plugin', () => {
usingEphemeralEncryptionKey: false,
},
});
- const pluginStart = plugin.start(coreStart, pluginsStart);
+ const pluginStart = await plugin.start(coreStart, pluginsStart);
await pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest());
});
@@ -241,7 +243,7 @@ describe('Actions Plugin', () => {
// coreMock.createSetup doesn't support Plugin generics
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await plugin.setup(coreSetup as any, pluginsSetup);
- const pluginStart = plugin.start(coreStart, pluginsStart);
+ const pluginStart = await plugin.start(coreStart, pluginsStart);
expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true);
await expect(
diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts
index ee50ee81d507c..413e6663105b8 100644
--- a/x-pack/plugins/actions/server/plugin.ts
+++ b/x-pack/plugins/actions/server/plugin.ts
@@ -116,6 +116,7 @@ export class ActionsPlugin implements Plugin, Plugi
private readonly config: Promise;
private readonly logger: Logger;
+ private actionsConfig?: ActionsConfig;
private serverBasePath?: string;
private taskRunnerFactory?: TaskRunnerFactory;
private actionTypeRegistry?: ActionTypeRegistry;
@@ -173,12 +174,12 @@ export class ActionsPlugin implements Plugin, Plugi
// get executions count
const taskRunnerFactory = new TaskRunnerFactory(actionExecutor);
- const actionsConfig = (await this.config) as ActionsConfig;
- const actionsConfigUtils = getActionsConfigurationUtilities(actionsConfig);
+ this.actionsConfig = (await this.config) as ActionsConfig;
+ const actionsConfigUtils = getActionsConfigurationUtilities(this.actionsConfig);
- for (const preconfiguredId of Object.keys(actionsConfig.preconfigured)) {
+ for (const preconfiguredId of Object.keys(this.actionsConfig.preconfigured)) {
this.preconfiguredActions.push({
- ...actionsConfig.preconfigured[preconfiguredId],
+ ...this.actionsConfig.preconfigured[preconfiguredId],
id: preconfiguredId,
isPreconfigured: true,
});
@@ -317,6 +318,14 @@ export class ActionsPlugin implements Plugin, Plugi
encryptedSavedObjectsClient,
actionTypeRegistry: actionTypeRegistry!,
preconfiguredActions,
+ proxySettings:
+ this.actionsConfig && this.actionsConfig.proxyUrl
+ ? {
+ proxyUrl: this.actionsConfig.proxyUrl,
+ proxyHeaders: this.actionsConfig.proxyHeaders,
+ rejectUnauthorizedCertificates: this.actionsConfig.rejectUnauthorizedCertificates,
+ }
+ : undefined,
});
taskRunnerFactory!.initialize({
diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts
index ecec45ade0460..bf7bd709a4a88 100644
--- a/x-pack/plugins/actions/server/types.ts
+++ b/x-pack/plugins/actions/server/types.ts
@@ -58,6 +58,7 @@ export interface ActionTypeExecutorOptions {
config: Config;
secrets: Secrets;
params: Params;
+ proxySettings?: ProxySettings;
}
export interface ActionResult {
@@ -140,3 +141,9 @@ export interface ActionTaskExecutorParams {
spaceId: string;
actionTaskParamsId: string;
}
+
+export interface ProxySettings {
+ proxyUrl: string;
+ proxyHeaders?: Record;
+ rejectUnauthorizedCertificates: boolean;
+}
diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts
index c3a132bc609d6..294a831c93459 100644
--- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts
+++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts
@@ -6,7 +6,7 @@
import { i18n } from '@kbn/i18n';
import { Params } from './alert_type_params';
-import { AlertExecutorOptions } from '../../../../alerts/server';
+import { AlertExecutorOptions, AlertInstanceContext } from '../../../../alerts/server';
// alert type context provided to actions
@@ -19,7 +19,7 @@ export interface ActionContext extends BaseActionContext {
message: string;
}
-export interface BaseActionContext {
+export interface BaseActionContext extends AlertInstanceContext {
// the aggType used in the alert
// the value of the aggField, if used, otherwise 'all documents'
group: string;
diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts
index c0522c08a7b96..01c2c2feb5b9a 100644
--- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts
+++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts
@@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import { AlertType, AlertExecutorOptions } from '../../types';
import { Params, ParamsSchema } from './alert_type_params';
-import { BaseActionContext, addMessages } from './action_context';
+import { ActionContext, BaseActionContext, addMessages } from './action_context';
import { TimeSeriesQuery } from './lib/time_series_query';
import { Service } from '../../types';
import { BUILT_IN_ALERTS_FEATURE_ID } from '../../../common';
@@ -19,7 +19,7 @@ const ActionGroupId = 'threshold met';
const ComparatorFns = getComparatorFns();
export const ComparatorFnNames = new Set(ComparatorFns.keys());
-export function getAlertType(service: Service): AlertType {
+export function getAlertType(service: Service): AlertType {
const { logger } = service;
const alertTypeName = i18n.translate('xpack.alertingBuiltins.indexThreshold.alertTypeTitle', {
@@ -118,9 +118,8 @@ export function getAlertType(service: Service): AlertType {
producer: BUILT_IN_ALERTS_FEATURE_ID,
};
- async function executor(options: AlertExecutorOptions) {
- const { alertId, name, services } = options;
- const params: Params = options.params as Params;
+ async function executor(options: AlertExecutorOptions) {
+ const { alertId, name, services, params } = options;
const compareFn = ComparatorFns.get(params.thresholdComparator);
if (compareFn == null) {
diff --git a/x-pack/plugins/alerts/README.md b/x-pack/plugins/alerts/README.md
index 10568abbe3c72..aab05cb0a7cfd 100644
--- a/x-pack/plugins/alerts/README.md
+++ b/x-pack/plugins/alerts/README.md
@@ -26,6 +26,7 @@ Table of Contents
- [`GET /api/alerts/_find`: Find alerts](#get-apialertfind-find-alerts)
- [`GET /api/alerts/alert/{id}`: Get alert](#get-apialertid-get-alert)
- [`GET /api/alerts/alert/{id}/state`: Get alert state](#get-apialertidstate-get-alert-state)
+ - [`GET /api/alerts/alert/{id}/status`: Get alert status](#get-apialertidstate-get-alert-status)
- [`GET /api/alerts/list_alert_types`: List alert types](#get-apialerttypes-list-alert-types)
- [`PUT /api/alerts/alert/{id}`: Update alert](#put-apialertid-update-alert)
- [`POST /api/alerts/alert/{id}/_enable`: Enable an alert](#post-apialertidenable-enable-an-alert)
@@ -504,6 +505,23 @@ Params:
|---|---|---|
|id|The id of the alert whose state you're trying to get.|string|
+### `GET /api/alerts/alert/{id}/status`: Get alert status
+
+Similar to the `GET state` call, but collects additional information from
+the event log.
+
+Params:
+
+|Property|Description|Type|
+|---|---|---|
+|id|The id of the alert whose status you're trying to get.|string|
+
+Query:
+
+|Property|Description|Type|
+|---|---|---|
+|dateStart|The date to start looking for alert events in the event log. Either an ISO date string, or a duration string indicating time since now.|string|
+
### `GET /api/alerts/list_alert_types`: List alert types
No parameters.
diff --git a/x-pack/plugins/alerts/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts
index b410b6aa0187e..3ff7ed742e810 100644
--- a/x-pack/plugins/alerts/common/alert.ts
+++ b/x-pack/plugins/alerts/common/alert.ts
@@ -6,6 +6,11 @@
import { SavedObjectAttributes } from 'kibana/server';
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type AlertTypeState = Record;
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type AlertTypeParams = Record;
+
export interface IntervalSchedule extends SavedObjectAttributes {
interval: string;
}
@@ -28,9 +33,7 @@ export interface Alert {
consumer: string;
schedule: IntervalSchedule;
actions: AlertAction[];
- // This will have to remain `any` until we can extend Alert Executors with generics
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- params: Record;
+ params: AlertTypeParams;
scheduledTaskId?: string;
createdBy: string | null;
updatedBy: string | null;
diff --git a/x-pack/plugins/alerts/common/alert_instance.ts b/x-pack/plugins/alerts/common/alert_instance.ts
index a6852f06efd34..0253a5c6919d6 100644
--- a/x-pack/plugins/alerts/common/alert_instance.ts
+++ b/x-pack/plugins/alerts/common/alert_instance.ts
@@ -17,6 +17,9 @@ export type AlertInstanceMeta = t.TypeOf;
const stateSchema = t.record(t.string, t.unknown);
export type AlertInstanceState = t.TypeOf;
+const contextSchema = t.record(t.string, t.unknown);
+export type AlertInstanceContext = t.TypeOf;
+
export const rawAlertInstance = t.partial({
state: stateSchema,
meta: metaSchema,
diff --git a/x-pack/plugins/alerts/common/alert_status.ts b/x-pack/plugins/alerts/common/alert_status.ts
new file mode 100644
index 0000000000000..517db6d6cb243
--- /dev/null
+++ b/x-pack/plugins/alerts/common/alert_status.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+type AlertStatusValues = 'OK' | 'Active' | 'Error';
+type AlertInstanceStatusValues = 'OK' | 'Active';
+
+export interface AlertStatus {
+ id: string;
+ name: string;
+ tags: string[];
+ alertTypeId: string;
+ consumer: string;
+ muteAll: boolean;
+ throttle: string | null;
+ enabled: boolean;
+ statusStartDate: string;
+ statusEndDate: string;
+ status: AlertStatusValues;
+ lastRun?: string;
+ errorMessages: Array<{ date: string; message: string }>;
+ instances: Record;
+}
+
+export interface AlertInstanceStatus {
+ status: AlertInstanceStatusValues;
+ muted: boolean;
+ activeStartDate?: string;
+}
diff --git a/x-pack/plugins/alerts/common/index.ts b/x-pack/plugins/alerts/common/index.ts
index b839c07a9db89..0922e164a3aa3 100644
--- a/x-pack/plugins/alerts/common/index.ts
+++ b/x-pack/plugins/alerts/common/index.ts
@@ -9,6 +9,7 @@ export * from './alert_type';
export * from './alert_instance';
export * from './alert_task_instance';
export * from './alert_navigation';
+export * from './alert_status';
export interface ActionGroup {
id: string;
diff --git a/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts b/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts
index 4d106178f86fb..661fb75f81c00 100644
--- a/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts
+++ b/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts
@@ -8,24 +8,26 @@ import {
AlertInstanceState,
RawAlertInstance,
rawAlertInstance,
+ AlertInstanceContext,
} from '../../common';
-import { State, Context } from '../types';
import { parseDuration } from '../lib';
-interface ScheduledExecutionOptions {
- actionGroup: string;
- context: Context;
- state: State;
-}
export type AlertInstances = Record;
-export class AlertInstance {
- private scheduledExecutionOptions?: ScheduledExecutionOptions;
+export class AlertInstance<
+ State extends AlertInstanceState = AlertInstanceState,
+ Context extends AlertInstanceContext = AlertInstanceContext
+> {
+ private scheduledExecutionOptions?: {
+ actionGroup: string;
+ context: Context;
+ state: State;
+ };
private meta: AlertInstanceMeta;
- private state: AlertInstanceState;
+ private state: State;
- constructor({ state = {}, meta = {} }: RawAlertInstance = {}) {
- this.state = state;
+ constructor({ state, meta = {} }: RawAlertInstance = {}) {
+ this.state = (state || {}) as State;
this.meta = meta;
}
@@ -62,11 +64,15 @@ export class AlertInstance {
return this.state;
}
- scheduleActions(actionGroup: string, context: Context = {}) {
+ scheduleActions(actionGroup: string, context?: Context) {
if (this.hasScheduledActions()) {
throw new Error('Alert instance execution has already been scheduled, cannot schedule twice');
}
- this.scheduledExecutionOptions = { actionGroup, context, state: this.state };
+ this.scheduledExecutionOptions = {
+ actionGroup,
+ context: (context || {}) as Context,
+ state: this.state,
+ };
return this;
}
diff --git a/x-pack/plugins/alerts/server/alert_type_registry.ts b/x-pack/plugins/alerts/server/alert_type_registry.ts
index 19d3bf13bd66d..7f34803b05a81 100644
--- a/x-pack/plugins/alerts/server/alert_type_registry.ts
+++ b/x-pack/plugins/alerts/server/alert_type_registry.ts
@@ -10,7 +10,13 @@ import { schema } from '@kbn/config-schema';
import typeDetect from 'type-detect';
import { RunContext, TaskManagerSetupContract } from '../../task_manager/server';
import { TaskRunnerFactory } from './task_runner';
-import { AlertType } from './types';
+import {
+ AlertType,
+ AlertTypeParams,
+ AlertTypeState,
+ AlertInstanceState,
+ AlertInstanceContext,
+} from './types';
interface ConstructorOptions {
taskManager: TaskManagerSetupContract;
@@ -59,7 +65,12 @@ export class AlertTypeRegistry {
return this.alertTypes.has(id);
}
- public register(alertType: AlertType) {
+ public register<
+ Params extends AlertTypeParams = AlertTypeParams,
+ State extends AlertTypeState = AlertTypeState,
+ InstanceState extends AlertInstanceState = AlertInstanceState,
+ InstanceContext extends AlertInstanceContext = AlertInstanceContext
+ >(alertType: AlertType) {
if (this.has(alertType.id)) {
throw new Error(
i18n.translate('xpack.alerts.alertTypeRegistry.register.duplicateAlertTypeError', {
@@ -71,18 +82,23 @@ export class AlertTypeRegistry {
);
}
alertType.actionVariables = normalizedActionVariables(alertType.actionVariables);
- this.alertTypes.set(alertIdSchema.validate(alertType.id), { ...alertType });
+ this.alertTypes.set(alertIdSchema.validate(alertType.id), { ...alertType } as AlertType);
this.taskManager.registerTaskDefinitions({
[`alerting:${alertType.id}`]: {
title: alertType.name,
type: `alerting:${alertType.id}`,
createTaskRunner: (context: RunContext) =>
- this.taskRunnerFactory.create(alertType, context),
+ this.taskRunnerFactory.create({ ...alertType } as AlertType, context),
},
});
}
- public get(id: string): AlertType {
+ public get<
+ Params extends AlertTypeParams = AlertTypeParams,
+ State extends AlertTypeState = AlertTypeState,
+ InstanceState extends AlertInstanceState = AlertInstanceState,
+ InstanceContext extends AlertInstanceContext = AlertInstanceContext
+ >(id: string): AlertType {
if (!this.has(id)) {
throw Boom.badRequest(
i18n.translate('xpack.alerts.alertTypeRegistry.get.missingAlertTypeError', {
@@ -93,7 +109,7 @@ export class AlertTypeRegistry {
})
);
}
- return this.alertTypes.get(id)!;
+ return this.alertTypes.get(id)! as AlertType;
}
public list(): Set {
diff --git a/x-pack/plugins/alerts/server/alerts_client.mock.ts b/x-pack/plugins/alerts/server/alerts_client.mock.ts
index be70e441b6fc5..b61139ae72c99 100644
--- a/x-pack/plugins/alerts/server/alerts_client.mock.ts
+++ b/x-pack/plugins/alerts/server/alerts_client.mock.ts
@@ -25,6 +25,7 @@ const createAlertsClientMock = () => {
muteInstance: jest.fn(),
unmuteInstance: jest.fn(),
listAlertTypes: jest.fn(),
+ getAlertStatus: jest.fn(),
};
return mocked;
};
diff --git a/x-pack/plugins/alerts/server/alerts_client.test.ts b/x-pack/plugins/alerts/server/alerts_client.test.ts
index c25e040ad09ce..d994269366ae6 100644
--- a/x-pack/plugins/alerts/server/alerts_client.test.ts
+++ b/x-pack/plugins/alerts/server/alerts_client.test.ts
@@ -11,16 +11,22 @@ import { taskManagerMock } from '../../task_manager/server/task_manager.mock';
import { alertTypeRegistryMock } from './alert_type_registry.mock';
import { alertsAuthorizationMock } from './authorization/alerts_authorization.mock';
import { TaskStatus } from '../../task_manager/server';
-import { IntervalSchedule } from './types';
+import { IntervalSchedule, RawAlert } from './types';
import { resolvable } from './test_utils';
import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks';
import { actionsClientMock, actionsAuthorizationMock } from '../../actions/server/mocks';
import { AlertsAuthorization } from './authorization/alerts_authorization';
import { ActionsAuthorization } from '../../actions/server';
+import { eventLogClientMock } from '../../event_log/server/mocks';
+import { QueryEventsBySavedObjectResult } from '../../event_log/server';
+import { SavedObject } from 'kibana/server';
+import { EventsFactory } from './lib/alert_status_from_event_log.test';
const taskManager = taskManagerMock.start();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
+const eventLogClient = eventLogClientMock.create();
+
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
@@ -39,6 +45,7 @@ const alertsClientParams: jest.Mocked = {
logger: loggingSystemMock.create().get(),
encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(),
+ getEventLogClient: jest.fn(),
};
beforeEach(() => {
@@ -91,17 +98,33 @@ beforeEach(() => {
async executor() {},
producer: 'alerts',
}));
+ alertsClientParams.getEventLogClient.mockResolvedValue(eventLogClient);
});
-const mockedDate = new Date('2019-02-12T21:01:22.479Z');
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const mockedDateString = '2019-02-12T21:01:22.479Z';
+const mockedDate = new Date(mockedDateString);
+const DateOriginal = Date;
+
+// A version of date that responds to `new Date(null|undefined)` and `Date.now()`
+// by returning a fixed date, otherwise should be same as Date.
+/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
(global as any).Date = class Date {
- constructor() {
- return mockedDate;
+ constructor(...args: unknown[]) {
+ // sometimes the ctor has no args, sometimes has a single `null` arg
+ if (args[0] == null) {
+ // @ts-ignore
+ return mockedDate;
+ } else {
+ // @ts-ignore
+ return new DateOriginal(...args);
+ }
}
static now() {
return mockedDate.getTime();
}
+ static parse(string: string) {
+ return DateOriginal.parse(string);
+ }
};
function getMockData(overwrites: Record = {}): CreateOptions['data'] {
@@ -2295,6 +2318,219 @@ describe('getAlertState()', () => {
});
});
+const AlertStatusFindEventsResult: QueryEventsBySavedObjectResult = {
+ page: 1,
+ per_page: 10000,
+ total: 0,
+ data: [],
+};
+
+const AlertStatusIntervalSeconds = 1;
+
+const BaseAlertStatusSavedObject: SavedObject = {
+ id: '1',
+ type: 'alert',
+ attributes: {
+ enabled: true,
+ name: 'alert-name',
+ tags: ['tag-1', 'tag-2'],
+ alertTypeId: '123',
+ consumer: 'alert-consumer',
+ schedule: { interval: `${AlertStatusIntervalSeconds}s` },
+ actions: [],
+ params: {},
+ createdBy: null,
+ updatedBy: null,
+ createdAt: mockedDateString,
+ apiKey: null,
+ apiKeyOwner: null,
+ throttle: null,
+ muteAll: false,
+ mutedInstanceIds: [],
+ },
+ references: [],
+};
+
+function getAlertStatusSavedObject(attributes: Partial = {}): SavedObject {
+ return {
+ ...BaseAlertStatusSavedObject,
+ attributes: { ...BaseAlertStatusSavedObject.attributes, ...attributes },
+ };
+}
+
+describe('getAlertStatus()', () => {
+ let alertsClient: AlertsClient;
+
+ beforeEach(() => {
+ alertsClient = new AlertsClient(alertsClientParams);
+ });
+
+ test('runs as expected with some event log data', async () => {
+ const alertSO = getAlertStatusSavedObject({ mutedInstanceIds: ['instance-muted-no-activity'] });
+ unsecuredSavedObjectsClient.get.mockResolvedValueOnce(alertSO);
+
+ const eventsFactory = new EventsFactory(mockedDateString);
+ const events = eventsFactory
+ .addExecute()
+ .addNewInstance('instance-currently-active')
+ .addNewInstance('instance-previously-active')
+ .addActiveInstance('instance-currently-active')
+ .addActiveInstance('instance-previously-active')
+ .advanceTime(10000)
+ .addExecute()
+ .addResolvedInstance('instance-previously-active')
+ .addActiveInstance('instance-currently-active')
+ .getEvents();
+ const eventsResult = {
+ ...AlertStatusFindEventsResult,
+ total: events.length,
+ data: events,
+ };
+ eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(eventsResult);
+
+ const dateStart = new Date(Date.now() - 60 * 1000).toISOString();
+
+ const result = await alertsClient.getAlertStatus({ id: '1', dateStart });
+ expect(result).toMatchInlineSnapshot(`
+ Object {
+ "alertTypeId": "123",
+ "consumer": "alert-consumer",
+ "enabled": true,
+ "errorMessages": Array [],
+ "id": "1",
+ "instances": Object {
+ "instance-currently-active": Object {
+ "activeStartDate": "2019-02-12T21:01:22.479Z",
+ "muted": false,
+ "status": "Active",
+ },
+ "instance-muted-no-activity": Object {
+ "activeStartDate": undefined,
+ "muted": true,
+ "status": "OK",
+ },
+ "instance-previously-active": Object {
+ "activeStartDate": undefined,
+ "muted": false,
+ "status": "OK",
+ },
+ },
+ "lastRun": "2019-02-12T21:01:32.479Z",
+ "muteAll": false,
+ "name": "alert-name",
+ "status": "Active",
+ "statusEndDate": "2019-02-12T21:01:22.479Z",
+ "statusStartDate": "2019-02-12T21:00:22.479Z",
+ "tags": Array [
+ "tag-1",
+ "tag-2",
+ ],
+ "throttle": null,
+ }
+ `);
+ });
+
+ // Further tests don't check the result of `getAlertStatus()`, as the result
+ // is just the result from the `alertStatusFromEventLog()`, which itself
+ // has a complete set of tests. These tests just make sure the data gets
+ // sent into `getAlertStatus()` as appropriate.
+
+ test('calls saved objects and event log client with default params', async () => {
+ unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertStatusSavedObject());
+ eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(AlertStatusFindEventsResult);
+
+ await alertsClient.getAlertStatus({ id: '1' });
+
+ expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
+ expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1);
+ expect(eventLogClient.findEventsBySavedObject.mock.calls[0]).toMatchInlineSnapshot(`
+ Array [
+ "alert",
+ "1",
+ Object {
+ "end": "2019-02-12T21:01:22.479Z",
+ "page": 1,
+ "per_page": 10000,
+ "sort_order": "desc",
+ "start": "2019-02-12T21:00:22.479Z",
+ },
+ ]
+ `);
+ // calculate the expected start/end date for one test
+ const { start, end } = eventLogClient.findEventsBySavedObject.mock.calls[0][2]!;
+ expect(end).toBe(mockedDateString);
+
+ const startMillis = Date.parse(start!);
+ const endMillis = Date.parse(end!);
+ const expectedDuration = 60 * AlertStatusIntervalSeconds * 1000;
+ expect(endMillis - startMillis).toBeGreaterThan(expectedDuration - 2);
+ expect(endMillis - startMillis).toBeLessThan(expectedDuration + 2);
+ });
+
+ test('calls event log client with start date', async () => {
+ unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertStatusSavedObject());
+ eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(AlertStatusFindEventsResult);
+
+ const dateStart = new Date(Date.now() - 60 * AlertStatusIntervalSeconds * 1000).toISOString();
+ await alertsClient.getAlertStatus({ id: '1', dateStart });
+
+ expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
+ expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1);
+ const { start, end } = eventLogClient.findEventsBySavedObject.mock.calls[0][2]!;
+
+ expect({ start, end }).toMatchInlineSnapshot(`
+ Object {
+ "end": "2019-02-12T21:01:22.479Z",
+ "start": "2019-02-12T21:00:22.479Z",
+ }
+ `);
+ });
+
+ test('calls event log client with relative start date', async () => {
+ unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertStatusSavedObject());
+ eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(AlertStatusFindEventsResult);
+
+ const dateStart = '2m';
+ await alertsClient.getAlertStatus({ id: '1', dateStart });
+
+ expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
+ expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1);
+ const { start, end } = eventLogClient.findEventsBySavedObject.mock.calls[0][2]!;
+
+ expect({ start, end }).toMatchInlineSnapshot(`
+ Object {
+ "end": "2019-02-12T21:01:22.479Z",
+ "start": "2019-02-12T20:59:22.479Z",
+ }
+ `);
+ });
+
+ test('invalid start date throws an error', async () => {
+ unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertStatusSavedObject());
+ eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(AlertStatusFindEventsResult);
+
+ const dateStart = 'ain"t no way this will get parsed as a date';
+ expect(alertsClient.getAlertStatus({ id: '1', dateStart })).rejects.toMatchInlineSnapshot(
+ `[Error: Invalid date for parameter dateStart: "ain"t no way this will get parsed as a date"]`
+ );
+ });
+
+ test('saved object get throws an error', async () => {
+ unsecuredSavedObjectsClient.get.mockRejectedValueOnce(new Error('OMG!'));
+ eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(AlertStatusFindEventsResult);
+
+ expect(alertsClient.getAlertStatus({ id: '1' })).rejects.toMatchInlineSnapshot(`[Error: OMG!]`);
+ });
+
+ test('findEvents throws an error', async () => {
+ unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertStatusSavedObject());
+ eventLogClient.findEventsBySavedObject.mockRejectedValueOnce(new Error('OMG 2!'));
+
+ // error eaten but logged
+ await alertsClient.getAlertStatus({ id: '1' });
+ });
+});
+
describe('find()', () => {
const listedTypes = new Set([
{
diff --git a/x-pack/plugins/alerts/server/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client.ts
index dd66ccc7a0256..80e021fc5cb6e 100644
--- a/x-pack/plugins/alerts/server/alerts_client.ts
+++ b/x-pack/plugins/alerts/server/alerts_client.ts
@@ -24,6 +24,7 @@ import {
IntervalSchedule,
SanitizedAlert,
AlertTaskState,
+ AlertStatus,
} from './types';
import { validateAlertTypeParams } from './lib';
import {
@@ -41,6 +42,11 @@ import {
WriteOperations,
ReadOperations,
} from './authorization/alerts_authorization';
+import { IEventLogClient } from '../../../plugins/event_log/server';
+import { parseIsoOrRelativeDate } from './lib/iso_or_relative_date';
+import { alertStatusFromEventLog } from './lib/alert_status_from_event_log';
+import { IEvent } from '../../event_log/server';
+import { parseDuration } from '../common/parse_duration';
export interface RegistryAlertTypeWithAuth extends RegistryAlertType {
authorizedConsumers: string[];
@@ -67,6 +73,7 @@ export interface ConstructorOptions {
createAPIKey: (name: string) => Promise;
invalidateAPIKey: (params: InvalidateAPIKeyParams) => Promise;
getActionsClient: () => Promise;
+ getEventLogClient: () => Promise;
}
export interface MuteOptions extends IndexType {
@@ -132,6 +139,11 @@ interface UpdateOptions {
};
}
+interface GetAlertStatusParams {
+ id: string;
+ dateStart?: string;
+}
+
export class AlertsClient {
private readonly logger: Logger;
private readonly getUserName: () => Promise;
@@ -147,6 +159,7 @@ export class AlertsClient {
) => Promise;
private readonly getActionsClient: () => Promise;
private readonly actionsAuthorization: ActionsAuthorization;
+ private readonly getEventLogClient: () => Promise;
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
constructor({
@@ -163,6 +176,7 @@ export class AlertsClient {
encryptedSavedObjectsClient,
getActionsClient,
actionsAuthorization,
+ getEventLogClient,
}: ConstructorOptions) {
this.logger = logger;
this.getUserName = getUserName;
@@ -177,6 +191,7 @@ export class AlertsClient {
this.encryptedSavedObjectsClient = encryptedSavedObjectsClient;
this.getActionsClient = getActionsClient;
this.actionsAuthorization = actionsAuthorization;
+ this.getEventLogClient = getEventLogClient;
}
public async create({ data, options }: CreateOptions): Promise {
@@ -269,6 +284,49 @@ export class AlertsClient {
}
}
+ public async getAlertStatus({ id, dateStart }: GetAlertStatusParams): Promise {
+ this.logger.debug(`getAlertStatus(): getting alert ${id}`);
+ const alert = await this.get({ id });
+ await this.authorization.ensureAuthorized(
+ alert.alertTypeId,
+ alert.consumer,
+ ReadOperations.GetAlertStatus
+ );
+
+ // default duration of status is 60 * alert interval
+ const dateNow = new Date();
+ const durationMillis = parseDuration(alert.schedule.interval) * 60;
+ const defaultDateStart = new Date(dateNow.valueOf() - durationMillis);
+ const parsedDateStart = parseDate(dateStart, 'dateStart', defaultDateStart);
+
+ const eventLogClient = await this.getEventLogClient();
+
+ this.logger.debug(`getAlertStatus(): search the event log for alert ${id}`);
+ let events: IEvent[];
+ try {
+ const queryResults = await eventLogClient.findEventsBySavedObject('alert', id, {
+ page: 1,
+ per_page: 10000,
+ start: parsedDateStart.toISOString(),
+ end: dateNow.toISOString(),
+ sort_order: 'desc',
+ });
+ events = queryResults.data;
+ } catch (err) {
+ this.logger.debug(
+ `alertsClient.getAlertStatus(): error searching event log for alert ${id}: ${err.message}`
+ );
+ events = [];
+ }
+
+ return alertStatusFromEventLog({
+ alert,
+ events,
+ dateStart: parsedDateStart.toISOString(),
+ dateEnd: dateNow.toISOString(),
+ });
+ }
+
public async find({
options: { fields, ...options } = {},
}: { options?: FindOptions } = {}): Promise {
@@ -283,7 +341,6 @@ export class AlertsClient {
? `${options.filter} and ${authorizationFilter}`
: authorizationFilter;
}
-
const {
page,
per_page: perPage,
@@ -886,3 +943,24 @@ export class AlertsClient {
return truncate(`Alerting: ${alertTypeId}/${alertName}`, { length: 256 });
}
}
+
+function parseDate(dateString: string | undefined, propertyName: string, defaultValue: Date): Date {
+ if (dateString === undefined) {
+ return defaultValue;
+ }
+
+ const parsedDate = parseIsoOrRelativeDate(dateString);
+ if (parsedDate === undefined) {
+ throw Boom.badRequest(
+ i18n.translate('xpack.alerts.alertsClient.getAlertStatus.invalidDate', {
+ defaultMessage: 'Invalid date for parameter {field}: "{dateValue}"',
+ values: {
+ field: propertyName,
+ dateValue: dateString,
+ },
+ })
+ );
+ }
+
+ return parsedDate;
+}
diff --git a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts
index 16b5af499bb90..a5eb371633f1e 100644
--- a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts
+++ b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts
@@ -22,6 +22,7 @@ import { actionsMock, actionsAuthorizationMock } from '../../actions/server/mock
import { featuresPluginMock } from '../../features/server/mocks';
import { AuditLogger } from '../../security/server';
import { ALERTS_FEATURE_ID } from '../common';
+import { eventLogMock } from '../../event_log/server/mocks';
jest.mock('./alerts_client');
jest.mock('./authorization/alerts_authorization');
@@ -42,6 +43,7 @@ const alertsClientFactoryParams: jest.Mocked = {
encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(),
actions: actionsMock.createStart(),
features,
+ eventLog: eventLogMock.createStart(),
};
const fakeRequest = ({
headers: {},
@@ -119,6 +121,7 @@ test('creates an alerts client with proper constructor arguments when security i
namespace: 'default',
getUserName: expect.any(Function),
getActionsClient: expect.any(Function),
+ getEventLogClient: expect.any(Function),
createAPIKey: expect.any(Function),
invalidateAPIKey: expect.any(Function),
encryptedSavedObjectsClient: alertsClientFactoryParams.encryptedSavedObjectsClient,
@@ -164,6 +167,7 @@ test('creates an alerts client with proper constructor arguments', async () => {
invalidateAPIKey: expect.any(Function),
encryptedSavedObjectsClient: alertsClientFactoryParams.encryptedSavedObjectsClient,
getActionsClient: expect.any(Function),
+ getEventLogClient: expect.any(Function),
});
});
diff --git a/x-pack/plugins/alerts/server/alerts_client_factory.ts b/x-pack/plugins/alerts/server/alerts_client_factory.ts
index 79b0ccaf1f0bc..83202424c9773 100644
--- a/x-pack/plugins/alerts/server/alerts_client_factory.ts
+++ b/x-pack/plugins/alerts/server/alerts_client_factory.ts
@@ -16,6 +16,7 @@ import { PluginStartContract as FeaturesPluginStart } from '../../features/serve
import { AlertsAuthorization } from './authorization/alerts_authorization';
import { AlertsAuthorizationAuditLogger } from './authorization/audit_logger';
import { Space } from '../../spaces/server';
+import { IEventLogClientService } from '../../../plugins/event_log/server';
export interface AlertsClientFactoryOpts {
logger: Logger;
@@ -28,6 +29,7 @@ export interface AlertsClientFactoryOpts {
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
actions: ActionsPluginStartContract;
features: FeaturesPluginStart;
+ eventLog: IEventLogClientService;
}
export class AlertsClientFactory {
@@ -42,6 +44,7 @@ export class AlertsClientFactory {
private encryptedSavedObjectsClient!: EncryptedSavedObjectsClient;
private actions!: ActionsPluginStartContract;
private features!: FeaturesPluginStart;
+ private eventLog!: IEventLogClientService;
public initialize(options: AlertsClientFactoryOpts) {
if (this.isInitialized) {
@@ -58,10 +61,11 @@ export class AlertsClientFactory {
this.encryptedSavedObjectsClient = options.encryptedSavedObjectsClient;
this.actions = options.actions;
this.features = options.features;
+ this.eventLog = options.eventLog;
}
public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): AlertsClient {
- const { securityPluginSetup, actions, features } = this;
+ const { securityPluginSetup, actions, eventLog, features } = this;
const spaceId = this.getSpaceId(request);
const authorization = new AlertsAuthorization({
authorization: securityPluginSetup?.authz,
@@ -135,6 +139,9 @@ export class AlertsClientFactory {
async getActionsClient() {
return actions.getActionsClientWithRequest(request);
},
+ async getEventLogClient() {
+ return eventLog.getClient(request);
+ },
});
}
}
diff --git a/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts b/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts
index 33a9a0bf0396e..b2a214eae9316 100644
--- a/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts
+++ b/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts
@@ -18,6 +18,7 @@ import { Space } from '../../../spaces/server';
export enum ReadOperations {
Get = 'get',
GetAlertState = 'getAlertState',
+ GetAlertStatus = 'getAlertStatus',
Find = 'find',
}
diff --git a/x-pack/plugins/alerts/server/index.ts b/x-pack/plugins/alerts/server/index.ts
index 515de771e7d6b..4c192a896c0c3 100644
--- a/x-pack/plugins/alerts/server/index.ts
+++ b/x-pack/plugins/alerts/server/index.ts
@@ -17,8 +17,11 @@ export {
AlertExecutorOptions,
AlertActionParams,
AlertServices,
- State,
+ AlertTypeState,
+ AlertTypeParams,
PartialAlert,
+ AlertInstanceState,
+ AlertInstanceContext,
} from './types';
export { PluginSetupContract, PluginStartContract } from './plugin';
export { FindResult } from './alerts_client';
diff --git a/x-pack/plugins/alerts/server/lib/alert_status_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_status_from_event_log.test.ts
new file mode 100644
index 0000000000000..15570d3032f24
--- /dev/null
+++ b/x-pack/plugins/alerts/server/lib/alert_status_from_event_log.test.ts
@@ -0,0 +1,464 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SanitizedAlert, AlertStatus } from '../types';
+import { IValidatedEvent } from '../../../event_log/server';
+import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from '../plugin';
+import { alertStatusFromEventLog } from './alert_status_from_event_log';
+
+const ONE_HOUR_IN_MILLIS = 60 * 60 * 1000;
+const dateStart = '2020-06-18T00:00:00.000Z';
+const dateEnd = dateString(dateStart, ONE_HOUR_IN_MILLIS);
+
+describe('alertStatusFromEventLog', () => {
+ test('no events and muted ids', async () => {
+ const alert = createAlert({});
+ const events: IValidatedEvent[] = [];
+ const status: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd });
+
+ expect(status).toMatchInlineSnapshot(`
+ Object {
+ "alertTypeId": "123",
+ "consumer": "alert-consumer",
+ "enabled": false,
+ "errorMessages": Array [],
+ "id": "alert-123",
+ "instances": Object {},
+ "lastRun": undefined,
+ "muteAll": false,
+ "name": "alert-name",
+ "status": "OK",
+ "statusEndDate": "2020-06-18T01:00:00.000Z",
+ "statusStartDate": "2020-06-18T00:00:00.000Z",
+ "tags": Array [],
+ "throttle": null,
+ }
+ `);
+ });
+
+ test('different alert properties', async () => {
+ const alert = createAlert({
+ id: 'alert-456',
+ alertTypeId: '456',
+ schedule: { interval: '100s' },
+ enabled: true,
+ name: 'alert-name-2',
+ tags: ['tag-1', 'tag-2'],
+ consumer: 'alert-consumer-2',
+ throttle: '1h',
+ muteAll: true,
+ });
+ const events: IValidatedEvent[] = [];
+ const status: AlertStatus = alertStatusFromEventLog({
+ alert,
+ events,
+ dateStart: dateString(dateEnd, ONE_HOUR_IN_MILLIS),
+ dateEnd: dateString(dateEnd, ONE_HOUR_IN_MILLIS * 2),
+ });
+
+ expect(status).toMatchInlineSnapshot(`
+ Object {
+ "alertTypeId": "456",
+ "consumer": "alert-consumer-2",
+ "enabled": true,
+ "errorMessages": Array [],
+ "id": "alert-456",
+ "instances": Object {},
+ "lastRun": undefined,
+ "muteAll": true,
+ "name": "alert-name-2",
+ "status": "OK",
+ "statusEndDate": "2020-06-18T03:00:00.000Z",
+ "statusStartDate": "2020-06-18T02:00:00.000Z",
+ "tags": Array [
+ "tag-1",
+ "tag-2",
+ ],
+ "throttle": "1h",
+ }
+ `);
+ });
+
+ test('two muted instances', async () => {
+ const alert = createAlert({
+ mutedInstanceIds: ['instance-1', 'instance-2'],
+ });
+ const events: IValidatedEvent[] = [];
+ const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd });
+
+ const { lastRun, status, instances } = alertStatus;
+ expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
+ Object {
+ "instances": Object {
+ "instance-1": Object {
+ "activeStartDate": undefined,
+ "muted": true,
+ "status": "OK",
+ },
+ "instance-2": Object {
+ "activeStartDate": undefined,
+ "muted": true,
+ "status": "OK",
+ },
+ },
+ "lastRun": undefined,
+ "status": "OK",
+ }
+ `);
+ });
+
+ test('active alert but no instances', async () => {
+ const alert = createAlert({});
+ const eventsFactory = new EventsFactory();
+ const events = eventsFactory.addExecute().advanceTime(10000).addExecute().getEvents();
+
+ const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd });
+
+ const { lastRun, status, instances } = alertStatus;
+ expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
+ Object {
+ "instances": Object {},
+ "lastRun": "2020-06-18T00:00:10.000Z",
+ "status": "OK",
+ }
+ `);
+ });
+
+ test('active alert with no instances but has errors', async () => {
+ const alert = createAlert({});
+ const eventsFactory = new EventsFactory();
+ const events = eventsFactory
+ .addExecute('oof!')
+ .advanceTime(10000)
+ .addExecute('rut roh!')
+ .getEvents();
+
+ const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd });
+
+ const { lastRun, status, errorMessages, instances } = alertStatus;
+ expect({ lastRun, status, errorMessages, instances }).toMatchInlineSnapshot(`
+ Object {
+ "errorMessages": Array [
+ Object {
+ "date": "2020-06-18T00:00:00.000Z",
+ "message": "oof!",
+ },
+ Object {
+ "date": "2020-06-18T00:00:10.000Z",
+ "message": "rut roh!",
+ },
+ ],
+ "instances": Object {},
+ "lastRun": "2020-06-18T00:00:10.000Z",
+ "status": "Error",
+ }
+ `);
+ });
+
+ test('alert with currently inactive instance', async () => {
+ const alert = createAlert({});
+ const eventsFactory = new EventsFactory();
+ const events = eventsFactory
+ .addExecute()
+ .addNewInstance('instance-1')
+ .addActiveInstance('instance-1')
+ .advanceTime(10000)
+ .addExecute()
+ .addResolvedInstance('instance-1')
+ .getEvents();
+
+ const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd });
+
+ const { lastRun, status, instances } = alertStatus;
+ expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
+ Object {
+ "instances": Object {
+ "instance-1": Object {
+ "activeStartDate": undefined,
+ "muted": false,
+ "status": "OK",
+ },
+ },
+ "lastRun": "2020-06-18T00:00:10.000Z",
+ "status": "OK",
+ }
+ `);
+ });
+
+ test('alert with currently inactive instance, no new-instance', async () => {
+ const alert = createAlert({});
+ const eventsFactory = new EventsFactory();
+ const events = eventsFactory
+ .addExecute()
+ .addActiveInstance('instance-1')
+ .advanceTime(10000)
+ .addExecute()
+ .addResolvedInstance('instance-1')
+ .getEvents();
+
+ const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd });
+
+ const { lastRun, status, instances } = alertStatus;
+ expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
+ Object {
+ "instances": Object {
+ "instance-1": Object {
+ "activeStartDate": undefined,
+ "muted": false,
+ "status": "OK",
+ },
+ },
+ "lastRun": "2020-06-18T00:00:10.000Z",
+ "status": "OK",
+ }
+ `);
+ });
+
+ test('alert with currently active instance', async () => {
+ const alert = createAlert({});
+ const eventsFactory = new EventsFactory();
+ const events = eventsFactory
+ .addExecute()
+ .addNewInstance('instance-1')
+ .addActiveInstance('instance-1')
+ .advanceTime(10000)
+ .addExecute()
+ .addActiveInstance('instance-1')
+ .getEvents();
+
+ const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd });
+
+ const { lastRun, status, instances } = alertStatus;
+ expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
+ Object {
+ "instances": Object {
+ "instance-1": Object {
+ "activeStartDate": "2020-06-18T00:00:00.000Z",
+ "muted": false,
+ "status": "Active",
+ },
+ },
+ "lastRun": "2020-06-18T00:00:10.000Z",
+ "status": "Active",
+ }
+ `);
+ });
+
+ test('alert with currently active instance, no new-instance', async () => {
+ const alert = createAlert({});
+ const eventsFactory = new EventsFactory();
+ const events = eventsFactory
+ .addExecute()
+ .addActiveInstance('instance-1')
+ .advanceTime(10000)
+ .addExecute()
+ .addActiveInstance('instance-1')
+ .getEvents();
+
+ const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd });
+
+ const { lastRun, status, instances } = alertStatus;
+ expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
+ Object {
+ "instances": Object {
+ "instance-1": Object {
+ "activeStartDate": undefined,
+ "muted": false,
+ "status": "Active",
+ },
+ },
+ "lastRun": "2020-06-18T00:00:10.000Z",
+ "status": "Active",
+ }
+ `);
+ });
+
+ test('alert with active and inactive muted alerts', async () => {
+ const alert = createAlert({ mutedInstanceIds: ['instance-1', 'instance-2'] });
+ const eventsFactory = new EventsFactory();
+ const events = eventsFactory
+ .addExecute()
+ .addNewInstance('instance-1')
+ .addActiveInstance('instance-1')
+ .addNewInstance('instance-2')
+ .addActiveInstance('instance-2')
+ .advanceTime(10000)
+ .addExecute()
+ .addActiveInstance('instance-1')
+ .addResolvedInstance('instance-2')
+ .getEvents();
+
+ const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd });
+
+ const { lastRun, status, instances } = alertStatus;
+ expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
+ Object {
+ "instances": Object {
+ "instance-1": Object {
+ "activeStartDate": "2020-06-18T00:00:00.000Z",
+ "muted": true,
+ "status": "Active",
+ },
+ "instance-2": Object {
+ "activeStartDate": undefined,
+ "muted": true,
+ "status": "OK",
+ },
+ },
+ "lastRun": "2020-06-18T00:00:10.000Z",
+ "status": "Active",
+ }
+ `);
+ });
+
+ test('alert with active and inactive alerts over many executes', async () => {
+ const alert = createAlert({});
+ const eventsFactory = new EventsFactory();
+ const events = eventsFactory
+ .addExecute()
+ .addNewInstance('instance-1')
+ .addActiveInstance('instance-1')
+ .addNewInstance('instance-2')
+ .addActiveInstance('instance-2')
+ .advanceTime(10000)
+ .addExecute()
+ .addActiveInstance('instance-1')
+ .addResolvedInstance('instance-2')
+ .advanceTime(10000)
+ .addExecute()
+ .addActiveInstance('instance-1')
+ .advanceTime(10000)
+ .addExecute()
+ .addActiveInstance('instance-1')
+ .getEvents();
+
+ const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd });
+
+ const { lastRun, status, instances } = alertStatus;
+ expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
+ Object {
+ "instances": Object {
+ "instance-1": Object {
+ "activeStartDate": "2020-06-18T00:00:00.000Z",
+ "muted": false,
+ "status": "Active",
+ },
+ "instance-2": Object {
+ "activeStartDate": undefined,
+ "muted": false,
+ "status": "OK",
+ },
+ },
+ "lastRun": "2020-06-18T00:00:30.000Z",
+ "status": "Active",
+ }
+ `);
+ });
+});
+
+function dateString(isoBaseDate: string, offsetMillis = 0): string {
+ return new Date(Date.parse(isoBaseDate) + offsetMillis).toISOString();
+}
+
+export class EventsFactory {
+ private events: IValidatedEvent[] = [];
+
+ constructor(private date: string = dateStart) {}
+
+ getEvents(): IValidatedEvent[] {
+ // ES normally returns events sorted newest to oldest, so we need to sort
+ // that way also
+ const events = this.events.slice();
+ events.sort((a, b) => -a!['@timestamp']!.localeCompare(b!['@timestamp']!));
+ return events;
+ }
+
+ getTime(): string {
+ return this.date;
+ }
+
+ advanceTime(millis: number): EventsFactory {
+ this.date = dateString(this.date, millis);
+ return this;
+ }
+
+ addExecute(errorMessage?: string): EventsFactory {
+ let event: IValidatedEvent = {
+ '@timestamp': this.date,
+ event: {
+ provider: EVENT_LOG_PROVIDER,
+ action: EVENT_LOG_ACTIONS.execute,
+ },
+ };
+
+ if (errorMessage) {
+ event = { ...event, error: { message: errorMessage } };
+ }
+
+ this.events.push(event);
+ return this;
+ }
+
+ addActiveInstance(instanceId: string): EventsFactory {
+ this.events.push({
+ '@timestamp': this.date,
+ event: {
+ provider: EVENT_LOG_PROVIDER,
+ action: EVENT_LOG_ACTIONS.activeInstance,
+ },
+ kibana: { alerting: { instance_id: instanceId } },
+ });
+ return this;
+ }
+
+ addNewInstance(instanceId: string): EventsFactory {
+ this.events.push({
+ '@timestamp': this.date,
+ event: {
+ provider: EVENT_LOG_PROVIDER,
+ action: EVENT_LOG_ACTIONS.newInstance,
+ },
+ kibana: { alerting: { instance_id: instanceId } },
+ });
+ return this;
+ }
+
+ addResolvedInstance(instanceId: string): EventsFactory {
+ this.events.push({
+ '@timestamp': this.date,
+ event: {
+ provider: EVENT_LOG_PROVIDER,
+ action: EVENT_LOG_ACTIONS.resolvedInstance,
+ },
+ kibana: { alerting: { instance_id: instanceId } },
+ });
+ return this;
+ }
+}
+
+function createAlert(overrides: Partial): SanitizedAlert {
+ return { ...BaseAlert, ...overrides };
+}
+
+const BaseAlert: SanitizedAlert = {
+ id: 'alert-123',
+ alertTypeId: '123',
+ schedule: { interval: '10s' },
+ enabled: false,
+ name: 'alert-name',
+ tags: [],
+ consumer: 'alert-consumer',
+ throttle: null,
+ muteAll: false,
+ mutedInstanceIds: [],
+ params: { bar: true },
+ actions: [],
+ createdBy: null,
+ updatedBy: null,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ apiKeyOwner: null,
+};
diff --git a/x-pack/plugins/alerts/server/lib/alert_status_from_event_log.ts b/x-pack/plugins/alerts/server/lib/alert_status_from_event_log.ts
new file mode 100644
index 0000000000000..606bd44c6990c
--- /dev/null
+++ b/x-pack/plugins/alerts/server/lib/alert_status_from_event_log.ts
@@ -0,0 +1,123 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { SanitizedAlert, AlertStatus, AlertInstanceStatus } from '../types';
+import { IEvent } from '../../../event_log/server';
+import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from '../plugin';
+
+export interface AlertStatusFromEventLogParams {
+ alert: SanitizedAlert;
+ events: IEvent[];
+ dateStart: string;
+ dateEnd: string;
+}
+
+export function alertStatusFromEventLog(params: AlertStatusFromEventLogParams): AlertStatus {
+ // initialize the result
+ const { alert, events, dateStart, dateEnd } = params;
+ const alertStatus: AlertStatus = {
+ id: alert.id,
+ name: alert.name,
+ tags: alert.tags,
+ alertTypeId: alert.alertTypeId,
+ consumer: alert.consumer,
+ statusStartDate: dateStart,
+ statusEndDate: dateEnd,
+ status: 'OK',
+ muteAll: alert.muteAll,
+ throttle: alert.throttle,
+ enabled: alert.enabled,
+ lastRun: undefined,
+ errorMessages: [],
+ instances: {},
+ };
+
+ const instances = new Map();
+
+ // loop through the events
+ // should be sorted newest to oldest, we want oldest to newest, so reverse
+ for (const event of events.reverse()) {
+ const timeStamp = event?.['@timestamp'];
+ if (timeStamp === undefined) continue;
+
+ const provider = event?.event?.provider;
+ if (provider !== EVENT_LOG_PROVIDER) continue;
+
+ const action = event?.event?.action;
+ if (action === undefined) continue;
+
+ if (action === EVENT_LOG_ACTIONS.execute) {
+ alertStatus.lastRun = timeStamp;
+
+ const errorMessage = event?.error?.message;
+ if (errorMessage !== undefined) {
+ alertStatus.status = 'Error';
+ alertStatus.errorMessages.push({
+ date: timeStamp,
+ message: errorMessage,
+ });
+ } else {
+ alertStatus.status = 'OK';
+ }
+
+ continue;
+ }
+
+ const instanceId = event?.kibana?.alerting?.instance_id;
+ if (instanceId === undefined) continue;
+
+ const status = getAlertInstanceStatus(instances, instanceId);
+ switch (action) {
+ case EVENT_LOG_ACTIONS.newInstance:
+ status.activeStartDate = timeStamp;
+ // intentionally no break here
+ case EVENT_LOG_ACTIONS.activeInstance:
+ status.status = 'Active';
+ break;
+ case EVENT_LOG_ACTIONS.resolvedInstance:
+ status.status = 'OK';
+ status.activeStartDate = undefined;
+ }
+ }
+
+ // set the muted status of instances
+ for (const instanceId of alert.mutedInstanceIds) {
+ getAlertInstanceStatus(instances, instanceId).muted = true;
+ }
+
+ // convert the instances map to object form
+ const instanceIds = Array.from(instances.keys()).sort();
+ for (const instanceId of instanceIds) {
+ alertStatus.instances[instanceId] = instances.get(instanceId)!;
+ }
+
+ // set the overall alert status to Active if appropriate
+ if (alertStatus.status !== 'Error') {
+ if (Array.from(instances.values()).some((instance) => instance.status === 'Active')) {
+ alertStatus.status = 'Active';
+ }
+ }
+
+ alertStatus.errorMessages.sort((a, b) => a.date.localeCompare(b.date));
+
+ return alertStatus;
+}
+
+// return an instance status object, creating and adding to the map if needed
+function getAlertInstanceStatus(
+ instances: Map,
+ instanceId: string
+): AlertInstanceStatus {
+ if (instances.has(instanceId)) return instances.get(instanceId)!;
+
+ const status: AlertInstanceStatus = {
+ status: 'OK',
+ muted: false,
+ activeStartDate: undefined,
+ };
+ instances.set(instanceId, status);
+ return status;
+}
diff --git a/x-pack/plugins/alerts/server/lib/iso_or_relative_date.test.ts b/x-pack/plugins/alerts/server/lib/iso_or_relative_date.test.ts
new file mode 100644
index 0000000000000..91272c1cca3b5
--- /dev/null
+++ b/x-pack/plugins/alerts/server/lib/iso_or_relative_date.test.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { parseIsoOrRelativeDate } from './iso_or_relative_date';
+
+describe('parseIsoOrRelativeDate', () => {
+ test('handles ISO dates', () => {
+ const date = new Date();
+ const parsedDate = parseIsoOrRelativeDate(date.toISOString());
+ expect(parsedDate?.valueOf()).toBe(date.valueOf());
+ });
+
+ test('handles relative dates', () => {
+ const hoursDiff = 1;
+ const date = new Date(Date.now() - hoursDiff * 60 * 60 * 1000);
+ const parsedDate = parseIsoOrRelativeDate(`${hoursDiff}h`);
+ const diff = Math.abs(parsedDate!.valueOf() - date.valueOf());
+ expect(diff).toBeLessThan(1000);
+ });
+
+ test('returns undefined for invalid date strings', () => {
+ const parsedDate = parseIsoOrRelativeDate('this shall not pass');
+ expect(parsedDate).toBeUndefined();
+ });
+});
diff --git a/x-pack/plugins/alerts/server/lib/iso_or_relative_date.ts b/x-pack/plugins/alerts/server/lib/iso_or_relative_date.ts
new file mode 100644
index 0000000000000..77c4eefa04439
--- /dev/null
+++ b/x-pack/plugins/alerts/server/lib/iso_or_relative_date.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { parseDuration } from '../../common/parse_duration';
+
+/**
+ * Parse an ISO date or NNx duration string as a Date
+ *
+ * @param dateString an ISO date or NNx "duration" string representing now-duration
+ * @returns a Date or undefined if the dateString was not valid
+ */
+export function parseIsoOrRelativeDate(dateString: string): Date | undefined {
+ const epochMillis = Date.parse(dateString);
+ if (!isNaN(epochMillis)) return new Date(epochMillis);
+
+ let millis: number;
+ try {
+ millis = parseDuration(dateString);
+ } catch (err) {
+ return;
+ }
+
+ return new Date(Date.now() - millis);
+}
diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts
index 5d69887bd5bf0..d5843bd531d84 100644
--- a/x-pack/plugins/alerts/server/plugin.ts
+++ b/x-pack/plugins/alerts/server/plugin.ts
@@ -38,6 +38,7 @@ import {
findAlertRoute,
getAlertRoute,
getAlertStateRoute,
+ getAlertStatusRoute,
listAlertTypesRoute,
updateAlertRoute,
enableAlertRoute,
@@ -57,16 +58,17 @@ import {
import { Services } from './types';
import { registerAlertsUsageCollector } from './usage';
import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task';
-import { IEventLogger, IEventLogService } from '../../event_log/server';
+import { IEventLogger, IEventLogService, IEventLogClientService } from '../../event_log/server';
import { PluginStartContract as FeaturesPluginStart } from '../../features/server';
import { setupSavedObjects } from './saved_objects';
-const EVENT_LOG_PROVIDER = 'alerting';
+export const EVENT_LOG_PROVIDER = 'alerting';
export const EVENT_LOG_ACTIONS = {
execute: 'execute',
executeAction: 'execute-action',
newInstance: 'new-instance',
resolvedInstance: 'resolved-instance',
+ activeInstance: 'active-instance',
};
export interface PluginSetupContract {
@@ -92,6 +94,7 @@ export interface AlertingPluginsStart {
taskManager: TaskManagerStartContract;
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
features: FeaturesPluginStart;
+ eventLog: IEventLogClientService;
}
export class AlertingPlugin {
@@ -189,6 +192,7 @@ export class AlertingPlugin {
findAlertRoute(router, this.licenseState);
getAlertRoute(router, this.licenseState);
getAlertStateRoute(router, this.licenseState);
+ getAlertStatusRoute(router, this.licenseState);
listAlertTypesRoute(router, this.licenseState);
updateAlertRoute(router, this.licenseState);
enableAlertRoute(router, this.licenseState);
@@ -235,6 +239,7 @@ export class AlertingPlugin {
},
actions: plugins.actions,
features: plugins.features,
+ eventLog: plugins.eventLog,
});
const getAlertsClientWithRequest = (request: KibanaRequest) => {
diff --git a/x-pack/plugins/alerts/server/routes/get_alert_status.test.ts b/x-pack/plugins/alerts/server/routes/get_alert_status.test.ts
new file mode 100644
index 0000000000000..1b4cb1941018b
--- /dev/null
+++ b/x-pack/plugins/alerts/server/routes/get_alert_status.test.ts
@@ -0,0 +1,105 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getAlertStatusRoute } from './get_alert_status';
+import { httpServiceMock } from 'src/core/server/mocks';
+import { mockLicenseState } from '../lib/license_state.mock';
+import { mockHandlerArguments } from './_mock_handler_arguments';
+import { SavedObjectsErrorHelpers } from 'src/core/server';
+import { alertsClientMock } from '../alerts_client.mock';
+import { AlertStatus } from '../types';
+
+const alertsClient = alertsClientMock.create();
+jest.mock('../lib/license_api_access.ts', () => ({
+ verifyApiAccess: jest.fn(),
+}));
+
+beforeEach(() => {
+ jest.resetAllMocks();
+});
+
+describe('getAlertStatusRoute', () => {
+ const dateString = new Date().toISOString();
+ const mockedAlertStatus: AlertStatus = {
+ id: '',
+ name: '',
+ tags: [],
+ alertTypeId: '',
+ consumer: '',
+ muteAll: false,
+ throttle: null,
+ enabled: false,
+ statusStartDate: dateString,
+ statusEndDate: dateString,
+ status: 'OK',
+ errorMessages: [],
+ instances: {},
+ };
+
+ it('gets alert status', async () => {
+ const licenseState = mockLicenseState();
+ const router = httpServiceMock.createRouter();
+
+ getAlertStatusRoute(router, licenseState);
+
+ const [config, handler] = router.get.mock.calls[0];
+
+ expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/status"`);
+
+ alertsClient.getAlertStatus.mockResolvedValueOnce(mockedAlertStatus);
+
+ const [context, req, res] = mockHandlerArguments(
+ { alertsClient },
+ {
+ params: {
+ id: '1',
+ },
+ query: {},
+ },
+ ['ok']
+ );
+
+ await handler(context, req, res);
+
+ expect(alertsClient.getAlertStatus).toHaveBeenCalledTimes(1);
+ expect(alertsClient.getAlertStatus.mock.calls[0]).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "dateStart": undefined,
+ "id": "1",
+ },
+ ]
+ `);
+
+ expect(res.ok).toHaveBeenCalled();
+ });
+
+ it('returns NOT-FOUND when alert is not found', async () => {
+ const licenseState = mockLicenseState();
+ const router = httpServiceMock.createRouter();
+
+ getAlertStatusRoute(router, licenseState);
+
+ const [, handler] = router.get.mock.calls[0];
+
+ alertsClient.getAlertStatus = jest
+ .fn()
+ .mockResolvedValueOnce(SavedObjectsErrorHelpers.createGenericNotFoundError('alert', '1'));
+
+ const [context, req, res] = mockHandlerArguments(
+ { alertsClient },
+ {
+ params: {
+ id: '1',
+ },
+ query: {},
+ },
+ ['notFound']
+ );
+
+ expect(await handler(context, req, res)).toEqual(undefined);
+ });
+});
diff --git a/x-pack/plugins/alerts/server/routes/get_alert_status.ts b/x-pack/plugins/alerts/server/routes/get_alert_status.ts
new file mode 100644
index 0000000000000..eab18c50189f4
--- /dev/null
+++ b/x-pack/plugins/alerts/server/routes/get_alert_status.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { schema, TypeOf } from '@kbn/config-schema';
+import {
+ IRouter,
+ RequestHandlerContext,
+ KibanaRequest,
+ IKibanaResponse,
+ KibanaResponseFactory,
+} from 'kibana/server';
+import { LicenseState } from '../lib/license_state';
+import { verifyApiAccess } from '../lib/license_api_access';
+import { BASE_ALERT_API_PATH } from '../../common';
+
+const paramSchema = schema.object({
+ id: schema.string(),
+});
+
+const querySchema = schema.object({
+ dateStart: schema.maybe(schema.string()),
+});
+
+export const getAlertStatusRoute = (router: IRouter, licenseState: LicenseState) => {
+ router.get(
+ {
+ path: `${BASE_ALERT_API_PATH}/alert/{id}/status`,
+ validate: {
+ params: paramSchema,
+ query: querySchema,
+ },
+ },
+ router.handleLegacyErrors(async function (
+ context: RequestHandlerContext,
+ req: KibanaRequest, TypeOf, unknown>,
+ res: KibanaResponseFactory
+ ): Promise {
+ verifyApiAccess(licenseState);
+ if (!context.alerting) {
+ return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
+ }
+ const alertsClient = context.alerting.getAlertsClient();
+ const { id } = req.params;
+ const { dateStart } = req.query;
+ const status = await alertsClient.getAlertStatus({ id, dateStart });
+ return res.ok({ body: status });
+ })
+ );
+};
diff --git a/x-pack/plugins/alerts/server/routes/index.ts b/x-pack/plugins/alerts/server/routes/index.ts
index f833a29c67bb9..4c6b1eb8e9b58 100644
--- a/x-pack/plugins/alerts/server/routes/index.ts
+++ b/x-pack/plugins/alerts/server/routes/index.ts
@@ -9,6 +9,7 @@ export { deleteAlertRoute } from './delete';
export { findAlertRoute } from './find';
export { getAlertRoute } from './get';
export { getAlertStateRoute } from './get_alert_state';
+export { getAlertStatusRoute } from './get_alert_status';
export { listAlertTypesRoute } from './list_alert_types';
export { updateAlertRoute } from './update';
export { enableAlertRoute } from './enable';
diff --git a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts
index c21d81779e5e0..bf074e2c60ee3 100644
--- a/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts
+++ b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts
@@ -5,12 +5,18 @@
*/
import { map } from 'lodash';
-import { AlertAction, State, Context, AlertType, AlertParams } from '../types';
import { Logger, KibanaRequest } from '../../../../../src/core/server';
import { transformActionParams } from './transform_action_params';
import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server';
import { IEventLogger, IEvent, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server';
import { EVENT_LOG_ACTIONS } from '../plugin';
+import {
+ AlertAction,
+ AlertInstanceState,
+ AlertInstanceContext,
+ AlertType,
+ AlertTypeParams,
+} from '../types';
interface CreateExecutionHandlerOptions {
alertId: string;
@@ -24,14 +30,14 @@ interface CreateExecutionHandlerOptions {
logger: Logger;
eventLogger: IEventLogger;
request: KibanaRequest;
- alertParams: AlertParams;
+ alertParams: AlertTypeParams;
}
interface ExecutionHandlerOptions {
actionGroup: string;
alertInstanceId: string;
- context: Context;
- state: State;
+ context: AlertInstanceContext;
+ state: AlertInstanceState;
}
export function createExecutionHandler({
diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts
index 4abe58de5a904..58b1fa4a123e1 100644
--- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts
+++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts
@@ -224,7 +224,7 @@ describe('Task Runner', () => {
`);
const eventLogger = taskRunnerFactoryInitializerParams.eventLogger;
- expect(eventLogger.logEvent).toHaveBeenCalledTimes(3);
+ expect(eventLogger.logEvent).toHaveBeenCalledTimes(4);
expect(eventLogger.logEvent).toHaveBeenCalledWith({
event: {
action: 'execute',
@@ -261,6 +261,25 @@ describe('Task Runner', () => {
},
message: "test:1: 'alert-name' created new instance: '1'",
});
+ expect(eventLogger.logEvent).toHaveBeenCalledWith({
+ event: {
+ action: 'active-instance',
+ },
+ kibana: {
+ alerting: {
+ instance_id: '1',
+ },
+ saved_objects: [
+ {
+ id: '1',
+ namespace: undefined,
+ rel: 'primary',
+ type: 'alert',
+ },
+ ],
+ },
+ message: "test:1: 'alert-name' active instance: '1'",
+ });
expect(eventLogger.logEvent).toHaveBeenCalledWith({
event: {
action: 'execute-action',
@@ -345,7 +364,7 @@ describe('Task Runner', () => {
`);
const eventLogger = taskRunnerFactoryInitializerParams.eventLogger;
- expect(eventLogger.logEvent).toHaveBeenCalledTimes(3);
+ expect(eventLogger.logEvent).toHaveBeenCalledTimes(4);
expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
@@ -388,6 +407,27 @@ describe('Task Runner', () => {
"message": "test:1: 'alert-name' created new instance: '1'",
},
],
+ Array [
+ Object {
+ "event": Object {
+ "action": "active-instance",
+ },
+ "kibana": Object {
+ "alerting": Object {
+ "instance_id": "1",
+ },
+ "saved_objects": Array [
+ Object {
+ "id": "1",
+ "namespace": undefined,
+ "rel": "primary",
+ "type": "alert",
+ },
+ ],
+ },
+ "message": "test:1: 'alert-name' active instance: '1'",
+ },
+ ],
Array [
Object {
"event": Object {
@@ -465,7 +505,7 @@ describe('Task Runner', () => {
`);
const eventLogger = taskRunnerFactoryInitializerParams.eventLogger;
- expect(eventLogger.logEvent).toHaveBeenCalledTimes(2);
+ expect(eventLogger.logEvent).toHaveBeenCalledTimes(3);
expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
@@ -508,6 +548,27 @@ describe('Task Runner', () => {
"message": "test:1: 'alert-name' resolved instance: '2'",
},
],
+ Array [
+ Object {
+ "event": Object {
+ "action": "active-instance",
+ },
+ "kibana": Object {
+ "alerting": Object {
+ "instance_id": "1",
+ },
+ "saved_objects": Array [
+ Object {
+ "id": "1",
+ "namespace": undefined,
+ "rel": "primary",
+ "type": "alert",
+ },
+ ],
+ },
+ "message": "test:1: 'alert-name' active instance: '1'",
+ },
+ ],
]
`);
});
diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts
index 04fea58f250a3..4c16d23b485b5 100644
--- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts
+++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts
@@ -355,41 +355,53 @@ interface GenerateNewAndResolvedInstanceEventsParams {
}
function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInstanceEventsParams) {
- const { currentAlertInstanceIds, originalAlertInstanceIds } = params;
+ const {
+ eventLogger,
+ alertId,
+ namespace,
+ currentAlertInstanceIds,
+ originalAlertInstanceIds,
+ } = params;
+
const newIds = without(currentAlertInstanceIds, ...originalAlertInstanceIds);
const resolvedIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds);
+ for (const id of resolvedIds) {
+ const message = `${params.alertLabel} resolved instance: '${id}'`;
+ logInstanceEvent(id, EVENT_LOG_ACTIONS.resolvedInstance, message);
+ }
+
for (const id of newIds) {
const message = `${params.alertLabel} created new instance: '${id}'`;
logInstanceEvent(id, EVENT_LOG_ACTIONS.newInstance, message);
}
- for (const id of resolvedIds) {
- const message = `${params.alertLabel} resolved instance: '${id}'`;
- logInstanceEvent(id, EVENT_LOG_ACTIONS.resolvedInstance, message);
+ for (const id of currentAlertInstanceIds) {
+ const message = `${params.alertLabel} active instance: '${id}'`;
+ logInstanceEvent(id, EVENT_LOG_ACTIONS.activeInstance, message);
}
- function logInstanceEvent(id: string, action: string, message: string) {
+ function logInstanceEvent(instanceId: string, action: string, message: string) {
const event: IEvent = {
event: {
action,
},
kibana: {
alerting: {
- instance_id: id,
+ instance_id: instanceId,
},
saved_objects: [
{
rel: SAVED_OBJECT_REL_PRIMARY,
type: 'alert',
- id: params.alertId,
- namespace: params.namespace,
+ id: alertId,
+ namespace,
},
],
},
message,
};
- params.eventLogger.logEvent(event);
+ eventLogger.logEvent(event);
}
}
diff --git a/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts
index 30f062eee3705..913fc51cb0f6e 100644
--- a/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts
+++ b/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts
@@ -6,7 +6,12 @@
import Mustache from 'mustache';
import { isString, cloneDeepWith } from 'lodash';
-import { AlertActionParams, State, Context, AlertParams } from '../types';
+import {
+ AlertActionParams,
+ AlertInstanceState,
+ AlertInstanceContext,
+ AlertTypeParams,
+} from '../types';
interface TransformActionParamsOptions {
alertId: string;
@@ -15,9 +20,9 @@ interface TransformActionParamsOptions {
tags?: string[];
alertInstanceId: string;
actionParams: AlertActionParams;
- state: State;
- context: Context;
- alertParams: AlertParams;
+ alertParams: AlertTypeParams;
+ state: AlertInstanceState;
+ context: AlertInstanceContext;
}
export function transformActionParams({
diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts
index 71ab35f7f434b..20943ba28885c 100644
--- a/x-pack/plugins/alerts/server/types.ts
+++ b/x-pack/plugins/alerts/server/types.ts
@@ -7,7 +7,6 @@
import { AlertInstance } from './alert_instance';
import { AlertTypeRegistry as OrigAlertTypeRegistry } from './alert_type_registry';
import { PluginSetupContract, PluginStartContract } from './plugin';
-import { Alert, AlertActionParams, ActionGroup } from '../common';
import { AlertsClient } from './alerts_client';
export * from '../common';
import {
@@ -17,13 +16,16 @@ import {
SavedObjectAttributes,
SavedObjectsClientContract,
} from '../../../../src/core/server';
+import {
+ Alert,
+ AlertActionParams,
+ ActionGroup,
+ AlertTypeParams,
+ AlertTypeState,
+ AlertInstanceContext,
+ AlertInstanceState,
+} from '../common';
-// This will have to remain `any` until we can extend Alert Executors with generics
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export type State = Record;
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export type Context = Record;
-export type AlertParams = Record;
export type WithoutQueryAndParams = Pick>;
export type GetServicesFunction = (request: KibanaRequest) => Services;
export type GetBasePathFunction = (spaceId?: string) => string;
@@ -44,18 +46,24 @@ export interface Services {
getLegacyScopedClusterClient(clusterClient: ILegacyClusterClient): ILegacyScopedClusterClient;
}
-export interface AlertServices extends Services {
- alertInstanceFactory: (id: string) => AlertInstance;
+export interface AlertServices<
+ InstanceState extends AlertInstanceState = AlertInstanceState,
+ InstanceContext extends AlertInstanceContext = AlertInstanceContext
+> extends Services {
+ alertInstanceFactory: (id: string) => AlertInstance;
}
-export interface AlertExecutorOptions {
+export interface AlertExecutorOptions<
+ Params extends AlertTypeParams = AlertTypeParams,
+ State extends AlertTypeState = AlertTypeState,
+ InstanceState extends AlertInstanceState = AlertInstanceState,
+ InstanceContext extends AlertInstanceContext = AlertInstanceContext
+> {
alertId: string;
startedAt: Date;
previousStartedAt: Date | null;
- services: AlertServices;
- // This will have to remain `any` until we can extend Alert Executors with generics
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- params: Record;
+ services: AlertServices;
+ params: Params;
state: State;
spaceId: string;
namespace?: string;
@@ -70,15 +78,24 @@ export interface ActionVariable {
description: string;
}
-export interface AlertType {
+export interface AlertType<
+ Params extends AlertTypeParams = AlertTypeParams,
+ State extends AlertTypeState = AlertTypeState,
+ InstanceState extends AlertInstanceState = AlertInstanceState,
+ InstanceContext extends AlertInstanceContext = AlertInstanceContext
+> {
id: string;
name: string;
validate?: {
- params?: { validate: (object: unknown) => AlertExecutorOptions['params'] };
+ params?: { validate: (object: unknown) => Params };
};
actionGroups: ActionGroup[];
defaultActionGroupId: ActionGroup['id'];
- executor: ({ services, params, state }: AlertExecutorOptions) => Promise;
+ executor: ({
+ services,
+ params,
+ state,
+ }: AlertExecutorOptions) => Promise;
producer: string;
actionVariables?: {
context?: ActionVariable[];
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap
index 0589fce727115..40522edc21b52 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap
@@ -104,16 +104,15 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = `
className="euiPopover__anchor"
>
@@ -521,16 +520,15 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
className="euiPopover__anchor"
>
@@ -1727,14 +1725,14 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
aria-controls="generated-id"
aria-current={true}
aria-label="Page 1 of 1"
- className="euiButtonEmpty euiButtonEmpty--text euiButtonEmpty--xSmall euiPaginationButton euiPaginationButton-isActive euiPaginationButton--hideOnMobile"
+ className="euiButtonEmpty euiButtonEmpty--text euiButtonEmpty--xSmall euiButtonEmpty-isDisabled euiPaginationButton euiPaginationButton-isActive euiPaginationButton--hideOnMobile"
data-test-subj="pagination-button-0"
disabled={true}
onClick={[Function]}
type="button"
>
{
- cy.$('example service').select();
- });
+ setTimeout(() => {
+ cy.$id('example service').select();
+ }, 0);
return (
@@ -59,9 +61,10 @@ storiesOf('app/ServiceMap/Popover', module)
info: {
propTablesExclude: [
CytoscapeContext.Provider,
+ EuiThemeProvider,
MockApmPluginContextWrapper,
MockUrlParamsContextProvider,
- EuiThemeProvider,
+ Popover,
],
source: false,
},
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx
index aee392b53298a..2a7d11bb57ca5 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx
@@ -288,43 +288,51 @@ storiesOf('app/ServiceMap/Cytoscape', module).add(
}
);
-storiesOf('app/ServiceMap/Cytoscape', module).add(
- 'node severity',
- () => {
- const elements = [
- { data: { id: 'undefined', 'service.name': 'severity: undefined' } },
- {
- data: {
- id: 'warning',
- 'service.name': 'severity: warning',
- severity: 'warning',
+storiesOf('app/ServiceMap/Cytoscape', module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'node severity',
+ () => {
+ const elements = [
+ {
+ data: {
+ id: 'undefined',
+ 'service.name': 'severity: undefined',
+ serviceAnomalyStats: { anomalyScore: undefined },
+ },
},
- },
- {
- data: {
- id: 'minor',
- 'service.name': 'severity: minor',
- severity: 'minor',
+ {
+ data: {
+ id: 'warning',
+ 'service.name': 'severity: warning',
+ serviceAnomalyStats: { anomalyScore: 0 },
+ },
},
- },
- {
- data: {
- id: 'major',
- 'service.name': 'severity: major',
- severity: 'major',
+ {
+ data: {
+ id: 'minor',
+ 'service.name': 'severity: minor',
+ serviceAnomalyStats: { anomalyScore: 40 },
+ },
},
- },
- {
- data: {
- id: 'critical',
- 'service.name': 'severity: critical',
- severity: 'critical',
+ {
+ data: {
+ id: 'major',
+ 'service.name': 'severity: major',
+ serviceAnomalyStats: { anomalyScore: 60 },
+ },
},
- },
- ];
- return ;
- },
- {
- info: { propTables: false, source: false },
- }
-);
+ {
+ data: {
+ id: 'critical',
+ 'service.name': 'severity: critical',
+ serviceAnomalyStats: { anomalyScore: 80 },
+ },
+ },
+ ];
+ return ;
+ },
+ {
+ info: { propTables: false, source: false },
+ }
+ );
diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap
index e46da26f7dcb0..281acf824b5c3 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap
@@ -104,11 +104,10 @@ NodeList [
type="button"
>
{
- return ;
+ return ;
},
{
- info: {
- source: false,
- },
- }
- )
- .add(
- 'sync=false',
- () => {
- return ;
- },
- {
- info: {
- source: false,
- },
+ showPanel: true,
+ info: { source: false },
}
)
.add(
@@ -36,9 +26,5 @@ storiesOf('app/TransactionDetails/SyncBadge', module)
() => {
return ;
},
- {
- info: {
- source: false,
- },
- }
+ { info: { source: false } }
);
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx
index 69cb091e76880..8e3d0effb97a6 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx
@@ -18,79 +18,88 @@ import {
inferredSpans,
} from './waterfallContainer.stories.data';
import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers';
+import { EuiThemeProvider } from '../../../../../../../observability/public';
-storiesOf('app/TransactionDetails/Waterfall', module).add(
- 'simple',
- () => {
- const waterfall = getWaterfall(
- simpleTrace as TraceAPIResponse,
- '975c8d5bfd1dd20b'
- );
- return (
-
- );
- },
- { info: { source: false } }
-);
+storiesOf('app/TransactionDetails/Waterfall', module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'example',
+ () => {
+ const waterfall = getWaterfall(
+ simpleTrace as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { propTablesExclude: [EuiThemeProvider], source: false } }
+ );
-storiesOf('app/TransactionDetails/Waterfall', module).add(
- 'with errors',
- () => {
- const waterfall = getWaterfall(
- (traceWithErrors as unknown) as TraceAPIResponse,
- '975c8d5bfd1dd20b'
- );
- return (
-
- );
- },
- { info: { source: false } }
-);
+storiesOf('app/TransactionDetails/Waterfall', module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'with errors',
+ () => {
+ const waterfall = getWaterfall(
+ (traceWithErrors as unknown) as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { propTablesExclude: [EuiThemeProvider], source: false } }
+ );
-storiesOf('app/TransactionDetails/Waterfall', module).add(
- 'child starts before parent',
- () => {
- const waterfall = getWaterfall(
- traceChildStartBeforeParent as TraceAPIResponse,
- '975c8d5bfd1dd20b'
- );
- return (
-
- );
- },
- { info: { source: false } }
-);
+storiesOf('app/TransactionDetails/Waterfall', module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'child starts before parent',
+ () => {
+ const waterfall = getWaterfall(
+ traceChildStartBeforeParent as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { propTablesExclude: [EuiThemeProvider], source: false } }
+ );
-storiesOf('app/TransactionDetails/Waterfall', module).add(
- 'inferred spans',
- () => {
- const waterfall = getWaterfall(
- inferredSpans as TraceAPIResponse,
- 'f2387d37260d00bd'
- );
- return (
-
- );
- },
- { info: { source: false } }
-);
+storiesOf('app/TransactionDetails/Waterfall', module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'inferred spans',
+ () => {
+ const waterfall = getWaterfall(
+ inferredSpans as TraceAPIResponse,
+ 'f2387d37260d00bd'
+ );
+ return (
+
+ );
+ },
+ { info: { propTablesExclude: [EuiThemeProvider], source: false } }
+ );
diff --git a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx
index ebcb1627984ad..632d53a9c63b6 100644
--- a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx
+++ b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx
@@ -13,23 +13,32 @@ import {
MockApmPluginContextWrapper,
} from '../../../context/ApmPluginContext/MockApmPluginContext';
-storiesOf('app/ErrorRateAlertTrigger', module).add('example', () => {
- const params = {
- threshold: 2,
- window: '5m',
- };
+storiesOf('app/ErrorRateAlertTrigger', module).add(
+ 'example',
+ () => {
+ const params = {
+ threshold: 2,
+ window: '5m',
+ };
- return (
-
-
- undefined}
- setAlertProperty={() => undefined}
- />
-
-
- );
-});
+ return (
+
+
+ undefined}
+ setAlertProperty={() => undefined}
+ />
+
+
+ );
+ },
+ {
+ info: {
+ propTablesExclude: [ErrorRateAlertTrigger, MockApmPluginContextWrapper],
+ source: false,
+ },
+ }
+);
diff --git a/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx b/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx
index 89f5bf28a4938..45fa3dd382266 100644
--- a/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx
+++ b/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx
@@ -27,6 +27,7 @@ storiesOf('app/LicensePrompt', module).add(
},
{
info: {
+ propTablesExclude: [ApmPluginContext.Provider, LicensePrompt],
source: false,
},
}
diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap
index 42c863e0accda..1fa440ebb794c 100644
--- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap
@@ -1,7 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TransactionActionMenu component should match the snapshot 1`] = `
-