diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 27532f0f377f9..96670b5d5107b 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -12,6 +12,7 @@
/src/plugins/advanced_settings/ @elastic/kibana-app
/src/plugins/charts/ @elastic/kibana-app
/src/plugins/discover/ @elastic/kibana-app
+/src/plugins/lens_oss/ @elastic/kibana-app
/src/plugins/management/ @elastic/kibana-app
/src/plugins/kibana_legacy/ @elastic/kibana-app
/src/plugins/timelion/ @elastic/kibana-app
diff --git a/.i18nrc.json b/.i18nrc.json
index 68e38d3976a68..653c67b535bff 100644
--- a/.i18nrc.json
+++ b/.i18nrc.json
@@ -59,6 +59,8 @@
"visTypeVislib": "src/plugins/vis_type_vislib",
"visTypeXy": "src/plugins/vis_type_xy",
"visualizations": "src/plugins/visualizations",
+ "lensOss": "src/plugins/lens_oss",
+ "mapsOss": "src/plugins/maps_oss",
"visualize": "src/plugins/visualize",
"apmOss": "src/plugins/apm_oss",
"usageCollection": "src/plugins/usage_collection"
diff --git a/.sass-lint.yml b/.sass-lint.yml
index 9eed50602f520..85599750b0cb8 100644
--- a/.sass-lint.yml
+++ b/.sass-lint.yml
@@ -6,6 +6,7 @@ files:
- 'src/plugins/vis_type_vislib/**/*.s+(a|c)ss'
- 'src/plugins/vis_type_vega/**/*.s+(a|c)ss'
- 'src/plugins/vis_type_xy/**/*.s+(a|c)ss'
+ - 'src/plugins/visualizations/public/wizard/**/*.s+(a|c)ss'
- 'x-pack/plugins/canvas/**/*.s+(a|c)ss'
- 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss'
- 'x-pack/plugins/lens/**/*.s+(a|c)ss'
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index 9a32f3b3adb3c..3c62c1fbca982 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -28,9 +28,7 @@ allowing users to configure their advanced settings, also known
as uiSettings within the code.
-|{kib-repo}blob/{branch}/src/plugins/apm_oss[apmOss]
-|WARNING: Missing README.
-
+|{kib-repo}blob/{branch}/src/plugins/apm_oss/README.asciidoc[apmOss]
|{kib-repo}blob/{branch}/src/plugins/bfetch/README.md[bfetch]
|bfetch allows to batch HTTP requests and streams responses back.
@@ -128,8 +126,13 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel.
|Utilities for building Kibana plugins.
-|{kib-repo}blob/{branch}/src/plugins/legacy_export[legacyExport]
-|WARNING: Missing README.
+|{kib-repo}blob/{branch}/src/plugins/legacy_export/README.md[legacyExport]
+|The legacyExport plugin adds support for the legacy saved objects export format.
+
+
+|{kib-repo}blob/{branch}/src/plugins/lens_oss/README.md[lensOss]
+|The lens_oss plugin registers the lens visualization on OSS.
+It is registered as disabled. The x-pack plugin should unregister this.
|{kib-repo}blob/{branch}/src/plugins/management/README.md[management]
@@ -142,6 +145,11 @@ management section itself.
|Internal objects used by the Coordinate, Region, and Vega visualizations.
+|{kib-repo}blob/{branch}/src/plugins/maps_oss/README.md[mapsOss]
+|The maps_oss plugin registers the maps visualization on OSS.
+It is registered as disabled. The x-pack plugin should unregister this.
+
+
|{kib-repo}blob/{branch}/src/plugins/navigation/README.md[navigation]
|The navigation plugins exports the TopNavMenu component.
It also provides a stateful version of it on the start contract.
@@ -156,12 +164,12 @@ Content is fetched from the remote (https://feeds.elastic.co and https://feeds-s
|Create choropleth maps. Display the results of a term-aggregation as e.g. countries, zip-codes, states.
-|{kib-repo}blob/{branch}/src/plugins/saved_objects[savedObjects]
-|WARNING: Missing README.
+|{kib-repo}blob/{branch}/src/plugins/saved_objects/README.md[savedObjects]
+|The savedObjects plugin exposes utilities to manipulate saved objects on the client side.
-|{kib-repo}blob/{branch}/src/plugins/saved_objects_management[savedObjectsManagement]
-|WARNING: Missing README.
+|{kib-repo}blob/{branch}/src/plugins/saved_objects_management/README.md[savedObjectsManagement]
+|The savedObjectsManagement plugin manages the Saved Objects management section.
|{kib-repo}blob/{branch}/src/plugins/saved_objects_tagging_oss/README.md[savedObjectsTaggingOss]
@@ -309,8 +317,8 @@ Failure to have auth enabled in Kibana will make for a broken UI. UI-based error
|Experimental Feature
-|{kib-repo}blob/{branch}/x-pack/plugins/cloud[cloud]
-|WARNING: Missing README.
+|{kib-repo}blob/{branch}/x-pack/plugins/cloud/README.md[cloud]
+|The cloud plugin adds cloud specific features to Kibana.
|{kib-repo}blob/{branch}/x-pack/plugins/code[code]
@@ -361,8 +369,8 @@ occuring in Kibana, initially just for the Make It Action project - alerts
and actions.
-|{kib-repo}blob/{branch}/x-pack/plugins/features[features]
-|WARNING: Missing README.
+|{kib-repo}blob/{branch}/x-pack/plugins/features/README.md[features]
+|The features plugin enhance Kibana with a per-feature privilege system.
|{kib-repo}blob/{branch}/x-pack/plugins/file_upload/README.md[fileUpload]
@@ -378,8 +386,8 @@ or dashboards from the Kibana instance, from both server and client-side plugins
|The GlobalSearchBar plugin provides a search interface for navigating Kibana. (It is the UI to the GlobalSearch plugin.)
-|{kib-repo}blob/{branch}/x-pack/plugins/global_search_providers[globalSearchProviders]
-|WARNING: Missing README.
+|{kib-repo}blob/{branch}/x-pack/plugins/global_search_providers/README.md[globalSearchProviders]
+|The globalSearchProviders plugin provides Kibana default search providers for the GlobalSearch plugin.
|{kib-repo}blob/{branch}/x-pack/plugins/graph/README.md[graph]
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 3afd5eaa6f1f7..9da31bb16b56b 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
@@ -9,6 +9,7 @@
```typescript
readonly links: {
readonly dashboard: {
+ readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
index 5249381969b98..01504aafe3bae 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 drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: 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 eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly visualize: Record<string, string>;
}
| |
+| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: 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 eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly visualize: Record<string, string>;
}
| |
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.fatalerror.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.fatalerror.md
new file mode 100644
index 0000000000000..e937fa8fd80e7
--- /dev/null
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.fatalerror.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [Embeddable](./kibana-plugin-plugins-embeddable-public.embeddable.md) > [fatalError](./kibana-plugin-plugins-embeddable-public.embeddable.fatalerror.md)
+
+## Embeddable.fatalError property
+
+Signature:
+
+```typescript
+fatalError?: Error;
+```
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.md
index 295cc10b1bb19..b1f1bed7541c3 100644
--- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.md
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.md
@@ -20,6 +20,7 @@ export declare abstract class EmbeddableError | |
| [id](./kibana-plugin-plugins-embeddable-public.embeddable.id.md) | | string
| |
| [input](./kibana-plugin-plugins-embeddable-public.embeddable.input.md) | | TEmbeddableInput
| |
| [isContainer](./kibana-plugin-plugins-embeddable-public.embeddable.iscontainer.md) | | boolean
| |
@@ -43,6 +44,7 @@ export declare abstract class Embeddable
+
+[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [Embeddable](./kibana-plugin-plugins-embeddable-public.embeddable.md) > [onFatalError](./kibana-plugin-plugins-embeddable-public.embeddable.onfatalerror.md)
+
+## Embeddable.onFatalError() method
+
+Signature:
+
+```typescript
+protected onFatalError(e: Error): void;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| e | Error
| |
+
+Returns:
+
+`void`
+
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iembeddable.fatalerror.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iembeddable.fatalerror.md
new file mode 100644
index 0000000000000..4b764a6ede079
--- /dev/null
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iembeddable.fatalerror.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [IEmbeddable](./kibana-plugin-plugins-embeddable-public.iembeddable.md) > [fatalError](./kibana-plugin-plugins-embeddable-public.iembeddable.fatalerror.md)
+
+## IEmbeddable.fatalError property
+
+If this embeddable has encountered a fatal error, that error will be stored here
+
+Signature:
+
+```typescript
+fatalError?: Error;
+```
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iembeddable.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iembeddable.md
index b3b6f961e56d1..f96477ed65a04 100644
--- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iembeddable.md
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iembeddable.md
@@ -15,6 +15,7 @@ export interface IEmbeddableobject | Extra abilities added to Embeddable by *_enhanced
plugins. |
+| [fatalError](./kibana-plugin-plugins-embeddable-public.iembeddable.fatalerror.md) | Error
| If this embeddable has encountered a fatal error, that error will be stored here |
| [id](./kibana-plugin-plugins-embeddable-public.iembeddable.id.md) | string
| A unique identifier for this embeddable. Mainly only used by containers to map their Panel States to a child embeddable instance. |
| [isContainer](./kibana-plugin-plugins-embeddable-public.iembeddable.iscontainer.md) | boolean
| Is this embeddable an instance of a Container class, can it contain nested embeddables? |
| [parent](./kibana-plugin-plugins-embeddable-public.iembeddable.parent.md) | IContainer
| If this embeddable is nested inside a container, this will contain a reference to its parent. |
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md
index 32a7151578658..8cc32ff698b38 100644
--- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element
+ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, onData$, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element
```
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md
index e4980ce04b9e2..92ea071b23dfc 100644
--- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md
@@ -18,6 +18,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams
| [dataAttrs](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.dataattrs.md) | string[]
| |
| [debounce](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md) | number
| |
| [expression](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.expression.md) | string | ExpressionAstExpression
| |
+| [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md) | <TData, TInspectorAdapters>(data: TData, adapters?: TInspectorAdapters) => void
| |
| [onEvent](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.onevent.md) | (event: ExpressionRendererEvent) => void
| |
| [padding](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.padding.md) | 'xs' | 's' | 'm' | 'l' | 'xl'
| |
| [reload$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.reload_.md) | Observable<unknown>
| An observable which can be used to re-run the expression without destroying the component |
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md
new file mode 100644
index 0000000000000..05ddb0b13a5be
--- /dev/null
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ReactExpressionRendererProps](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md) > [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md)
+
+## ReactExpressionRendererProps.onData$ property
+
+Signature:
+
+```typescript
+onData$?: (data: TData, adapters?: TInspectorAdapters) => void;
+```
diff --git a/docs/management/images/management-index-patterns.png b/docs/management/images/management-index-patterns.png
deleted file mode 100644
index 232d32893b96d..0000000000000
Binary files a/docs/management/images/management-index-patterns.png and /dev/null differ
diff --git a/docs/management/managing-fields.asciidoc b/docs/management/managing-fields.asciidoc
index 3734655edd91b..1b9d22699d359 100644
--- a/docs/management/managing-fields.asciidoc
+++ b/docs/management/managing-fields.asciidoc
@@ -1,70 +1,29 @@
[[managing-fields]]
-== Index patterns and fields
+== Field management
-The *Index patterns* UI helps you create and manage
-the index patterns that retrieve your data from {es}.
+Whenever possible,
+{kib} uses the same field type for display as {es}. However, a few field types
+{es} supports are not available in {kib}. Use field formatters to customize how your
+fields are displayed in Kibana, regardless of how they are stored in {es}.
-[role="screenshot"]
-image::images/management-index-patterns.png[]
-
-[float]
-=== Required permissions
-
-The `Index Pattern Management` {kib} privilege is required to access the *Index patterns* UI.
-
-To add the privilege, open the menu, then click *Stack Management > Roles*.
-
-[float]
-=== Create an index pattern
-
-An index pattern is the glue that connects {kib} to your {es} data. Create an
-index pattern whenever you load your own data into {kib}. To get started,
-click *Create index pattern*, and then follow the guided steps. Refer to
-<> for the types of index patterns
-that you can create.
-
-[float]
-=== Manage your index pattern
-
-To view the fields and associated data types in an index pattern, click its name in
-the *Index patterns* overview.
-
-[role="screenshot"]
-image::management/index-patterns/images/new-index-pattern.png["Index files and data types"]
-
-Use the icons to perform the following actions:
+Kibana provides these field formatters:
-* [[set-default-pattern]]*Set the default index pattern.* {kib} uses a badge to make users
-aware of which index pattern is the default. The first pattern
-you create is automatically designated as the default pattern. The default
-index pattern is loaded when you open *Discover*.
+* <>
+* <>
+* <>
+* <>
-* *Refresh the index fields list.* You can refresh the index fields list to
-pick up any newly-added fields. Doing so also resets the {kib} popularity counters
-for the fields. The popularity counters are used in *Discover* to sort fields in lists.
+To format a field:
-* [[delete-pattern]]*Delete the index pattern.* This action removes the pattern from the list of
-Saved Objects in {kib}. You will not be able to recover field formatters,
-scripted fields, source filters, and field popularity data associated with the index pattern.
-Deleting an index pattern does
-not remove any indices or data documents from {es}.
+. Open the main menu, and click *Stack Management > Index Patterns*.
+. Click the index pattern that contains the field you want to format.
+. Find the field you want to format and click the edit icon (image:management/index-patterns/images/edit_icon.png[]).
+. Select a format and fill in the details.
+
-WARNING: Deleting an index pattern breaks all visualizations, saved searches, and
-other saved objects that reference the pattern.
-
-[float]
-=== Edit a field
-
-To edit a field's properties, click the edit icon
-image:management/index-patterns/images/edit_icon.png[] in the detail view.
-You can set the field's format and popularity value.
+[role="screenshot"]
+image:management/index-patterns/images/edit-field-format.png["Edit field format"]
-Kibana has field formatters for the following field types:
-* <>
-* <>
-* <>
-* <>
[[field-formatters-string]]
=== String field formatters
@@ -121,12 +80,8 @@ WARNING: Computing data on the fly with scripted fields can be very resource int
{kib} performance. Keep in mind that there's no built-in validation of a scripted field. If your scripts are
buggy, you'll get exceptions whenever you try to view the dynamically generated data.
-When you define a scripted field in {kib}, you have a choice of scripting languages. In 5.0 and later, the default
-options are {ref}/modules-scripting-expression.html[Lucene expressions] and {ref}/modules-scripting-painless.html[Painless].
-While you can use other scripting languages if you enable dynamic scripting for them in {es}, this is not recommended
-because they cannot be sufficiently {ref}/modules-scripting-security.html[sandboxed].
-
-WARNING: In 5.0 and later, Groovy, JavaScript, and Python scripting are deprecated and unsupported.
+When you define a scripted field in {kib}, you have a choice of the {ref}/modules-scripting-expression.html[Lucene expressions] or the
+{ref}/modules-scripting-painless.html[Painless] scripting language.
You can reference any single value numeric field in your expressions, for example:
diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc
index b32c340df4adf..79fa9a642428a 100644
--- a/docs/settings/apm-settings.asciidoc
+++ b/docs/settings/apm-settings.asciidoc
@@ -43,6 +43,9 @@ Changing these settings may disable features of the APM App.
| `xpack.apm.enabled`
| Set to `false` to disable the APM app. Defaults to `true`.
+| `xpack.apm.maxServiceEnvironments`
+ | Maximum number of unique service environments recognized by the UI. Defaults to `100`.
+
| `xpack.apm.serviceMapFingerprintBucketSize`
| Maximum number of unique transaction combinations sampled for generating service map focused on a specific service. Defaults to `100`.
diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc
index 6b01094f7248a..12043ead28d55 100644
--- a/docs/settings/security-settings.asciidoc
+++ b/docs/settings/security-settings.asciidoc
@@ -92,8 +92,8 @@ The valid settings in the `xpack.security.authc.providers` namespace vary depend
`..icon` {ess-icon}
| Custom icon for the provider entry displayed on the Login Selector UI.
-| `xpack.security.authc.providers.`
-`..showInSelector` {ess-icon}
+| `xpack.security.authc.providers..`
+`.showInSelector` {ess-icon}
| 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|
@@ -103,10 +103,31 @@ The valid settings in the `xpack.security.authc.providers` namespace vary depend
You are unable to set this setting to `false` for `basic` and `token` authentication providers.
============
-| `xpack.security.authc.providers.`
-`..accessAgreement.message` {ess-icon}
+| `xpack.security.authc.providers..`
+`.accessAgreement.message` {ess-icon}
| Access agreement text in Markdown format. For more information, refer to <>.
+| [[xpack-security-provider-session-idleTimeout]] `xpack.security.authc.providers..`
+`.session.idleTimeout` {ess-icon}
+| Ensures that user sessions will expire after a period of inactivity. Setting this to `0` will prevent sessions from expiring because of inactivity. By default, this setting is equal to <>.
+
+2+a|
+[TIP]
+============
+Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w').
+============
+
+| [[xpack-security-provider-session-lifespan]] `xpack.security.authc.providers..`
+`.session.lifespan` {ess-icon}
+| Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If
+this is set to `0`, user sessions could stay active indefinitely. By default, this setting is equal to <>.
+
+2+a|
+[TIP]
+============
+Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w').
+============
+
|===
[float]
@@ -210,32 +231,32 @@ You can configure the following settings in the `kibana.yml` file.
|[[xpack-session-idleTimeout]] `xpack.security.session.idleTimeout` {ess-icon}
| Ensures that user sessions will expire after a period of inactivity. This and <> are both
-highly recommended. By default, this setting is not set.
+highly recommended. You can also specify this setting for <>. If this is _not_ set or set to `0`, then sessions will never expire due to inactivity. 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. '20m', '24h', '7d', '1w').
+Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w').
============
|[[xpack-session-lifespan]] `xpack.security.session.lifespan` {ess-icon}
- | 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 <> are both highly
-recommended. By default, this setting is not set.
+ | Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If
+this is _not_ set or set to `0`, user sessions could stay active indefinitely. This and <> are both highly
+recommended. You can also specify this setting for <>. 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. '20m', '24h', '7d', '1w').
+Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w').
============
-| `xpack.security.session.cleanupInterval`
+| `xpack.security.session.cleanupInterval` {ess-icon}
| 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.
2+a|
[TIP]
============
-The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w').
+Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w').
============
|===
diff --git a/docs/user/alerting/alerting-getting-started.asciidoc b/docs/user/alerting/alerting-getting-started.asciidoc
index f8656b87cbe04..2b22b49375676 100644
--- a/docs/user/alerting/alerting-getting-started.asciidoc
+++ b/docs/user/alerting/alerting-getting-started.asciidoc
@@ -169,12 +169,15 @@ If you are using an *on-premises* Elastic Stack deployment with <>
+* <>
* <>
* <>
* <>
-See <> for more information on configuring roles that provide access to these features.
+See <> for more information on configuring roles that provide access to these features.
+Also note that a user will need +read+ privileges for the *Actions and Connectors* feature to attach actions to an alert or to edit an alert that has an action attached to it.
[float]
[[alerting-spaces]]
diff --git a/package.json b/package.json
index 44a0c833eae27..ade567c840da7 100644
--- a/package.json
+++ b/package.json
@@ -93,6 +93,7 @@
"**/minimist": "^1.2.5",
"**/node-jose/node-forge": "^0.10.0",
"**/request": "^2.88.2",
+ "**/trim": "0.0.3",
"**/typescript": "4.0.2"
},
"engines": {
@@ -236,6 +237,7 @@
"markdown-it": "^10.0.0",
"md5": "^2.1.0",
"mime": "^2.4.4",
+ "mime-types": "^2.1.27",
"mini-css-extract-plugin": "0.8.0",
"minimatch": "^3.0.4",
"moment": "^2.24.0",
@@ -567,7 +569,7 @@
"@types/zen-observable": "^0.8.0",
"@typescript-eslint/eslint-plugin": "^3.10.0",
"@typescript-eslint/parser": "^3.10.0",
- "@welldone-software/why-did-you-render": "^4.0.0",
+ "@welldone-software/why-did-you-render": "^5.0.0",
"@yarnpkg/lockfile": "^1.1.0",
"abab": "^1.0.4",
"angular-aria": "^1.8.0",
@@ -842,15 +844,13 @@
"vinyl-fs": "^3.0.3",
"wait-on": "^5.0.1",
"watchpack": "^1.6.0",
- "webpack-cli": "^3.3.10",
- "webpack-dev-server": "^3.8.2",
+ "webpack-cli": "^3.3.12",
+ "webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2",
"write-pkg": "^4.0.0",
"xml-crypto": "^2.0.0",
"xmlbuilder": "13.0.2",
"yargs": "^15.4.1",
- "yeoman-generator": "1.1.1",
- "yo": "2.0.6",
"zlib": "^1.0.5"
}
-}
\ No newline at end of file
+}
diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts
index 3910ee3235caf..64b02f104a1f1 100644
--- a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts
+++ b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts
@@ -18,7 +18,7 @@
*/
import { ConfigDeprecationLogger } from './types';
-import { configDeprecationFactory } from './deprecation_factory';
+import { configDeprecationFactory, copyFromRoot } from './deprecation_factory';
describe('DeprecationFactory', () => {
const { rename, unused, renameFromRoot, unusedFromRoot } = configDeprecationFactory;
@@ -250,6 +250,89 @@ describe('DeprecationFactory', () => {
});
});
+ describe('copyFromRoot', () => {
+ it('copies a property to a different namespace', () => {
+ const rawConfig = {
+ originplugin: {
+ deprecated: 'toberenamed',
+ valid: 'valid',
+ },
+ destinationplugin: {
+ property: 'value',
+ },
+ };
+ const processed = copyFromRoot('originplugin.deprecated', 'destinationplugin.renamed')(
+ rawConfig,
+ 'does-not-matter',
+ logger
+ );
+ expect(processed).toEqual({
+ originplugin: {
+ deprecated: 'toberenamed',
+ valid: 'valid',
+ },
+ destinationplugin: {
+ renamed: 'toberenamed',
+ property: 'value',
+ },
+ });
+ expect(deprecationMessages.length).toEqual(0);
+ });
+
+ it('does not alter config if origin property is not present', () => {
+ const rawConfig = {
+ myplugin: {
+ new: 'new',
+ valid: 'valid',
+ },
+ someOtherPlugin: {
+ property: 'value',
+ },
+ };
+ const processed = copyFromRoot('myplugin.deprecated', 'myplugin.new')(
+ rawConfig,
+ 'does-not-matter',
+ logger
+ );
+ expect(processed).toEqual({
+ myplugin: {
+ new: 'new',
+ valid: 'valid',
+ },
+ someOtherPlugin: {
+ property: 'value',
+ },
+ });
+ expect(deprecationMessages.length).toEqual(0);
+ });
+
+ it('does not alter config if they both exist', () => {
+ const rawConfig = {
+ myplugin: {
+ deprecated: 'deprecated',
+ renamed: 'renamed',
+ },
+ someOtherPlugin: {
+ property: 'value',
+ },
+ };
+ const processed = copyFromRoot('myplugin.deprecated', 'someOtherPlugin.property')(
+ rawConfig,
+ 'does-not-matter',
+ logger
+ );
+ expect(processed).toEqual({
+ myplugin: {
+ deprecated: 'deprecated',
+ renamed: 'renamed',
+ },
+ someOtherPlugin: {
+ property: 'value',
+ },
+ });
+ });
+ });
+
describe('unused', () => {
it('removes the unused property from the config and logs a warning is present', () => {
const rawConfig = {
diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.ts b/packages/kbn-config/src/deprecation/deprecation_factory.ts
index 0598347d2cffc..70a55fedf05be 100644
--- a/packages/kbn-config/src/deprecation/deprecation_factory.ts
+++ b/packages/kbn-config/src/deprecation/deprecation_factory.ts
@@ -56,6 +56,26 @@ const _rename = (
return config;
};
+const _copy = (
+ config: Record,
+ rootPath: string,
+ originKey: string,
+ destinationKey: string
+) => {
+ const originPath = getPath(rootPath, originKey);
+ const originValue = get(config, originPath);
+ if (originValue === undefined) {
+ return config;
+ }
+
+ const destinationPath = getPath(rootPath, destinationKey);
+ const destinationValue = get(config, destinationPath);
+ if (destinationValue === undefined) {
+ set(config, destinationPath, originValue);
+ }
+ return config;
+};
+
const _unused = (
config: Record,
rootPath: string,
@@ -80,6 +100,12 @@ const renameFromRoot = (oldKey: string, newKey: string, silent?: boolean): Confi
log
) => _rename(config, '', log, oldKey, newKey, silent);
+export const copyFromRoot = (originKey: string, destinationKey: string): ConfigDeprecation => (
+ config,
+ rootPath,
+ log
+) => _copy(config, '', originKey, destinationKey);
+
const unused = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) =>
_unused(config, rootPath, log, unusedKey);
diff --git a/packages/kbn-config/src/deprecation/index.ts b/packages/kbn-config/src/deprecation/index.ts
index 504dbfeeb001a..4609b7b1d62d0 100644
--- a/packages/kbn-config/src/deprecation/index.ts
+++ b/packages/kbn-config/src/deprecation/index.ts
@@ -24,5 +24,5 @@ export {
ConfigDeprecationFactory,
ConfigDeprecationProvider,
} from './types';
-export { configDeprecationFactory } from './deprecation_factory';
+export { configDeprecationFactory, copyFromRoot } from './deprecation_factory';
export { applyDeprecations } from './apply_deprecations';
diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts
index 68609c6d5c7c3..b834568e8b3ae 100644
--- a/packages/kbn-config/src/index.ts
+++ b/packages/kbn-config/src/index.ts
@@ -25,6 +25,7 @@ export {
ConfigDeprecationLogger,
ConfigDeprecationProvider,
ConfigDeprecationWithContext,
+ copyFromRoot,
} from './deprecation';
export { RawConfigurationProvider, RawConfigService, getConfigFromFiles } from './raw';
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 11e977c74cf22..701b7cab21600 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -45,6 +45,7 @@ pageLoadAssetSize:
kibanaReact: 161921
kibanaUtils: 198829
lens: 96624
+ lensOss: 19341
licenseManagement: 41817
licensing: 39008
lists: 183665
@@ -53,6 +54,7 @@ pageLoadAssetSize:
maps: 183610
mapsLegacy: 116817
mapsLegacyLicensing: 20214
+ mapsOss: 19284
ml: 82187
monitoring: 50000
navigation: 37269
diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js
index a780ec96dd956..b7d9803059aa8 100644
--- a/packages/kbn-pm/dist/index.js
+++ b/packages/kbn-pm/dist/index.js
@@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; });
-/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(506);
+/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(505);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; });
/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248);
@@ -106,7 +106,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(251);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "transformDependencies", function() { return _utils_package_json__WEBPACK_IMPORTED_MODULE_4__["transformDependencies"]; });
-/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(505);
+/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(504);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; });
/*
@@ -150,7 +150,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5);
/* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(128);
-/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(499);
+/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(498);
/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246);
/*
* Licensed to Elasticsearch B.V. under one or more contributor
@@ -8897,9 +8897,9 @@ exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter;
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; });
/* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(129);
-/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(365);
-/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(398);
-/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(399);
+/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(366);
+/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(397);
+/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(398);
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
@@ -8942,10 +8942,10 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(246);
/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(247);
/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248);
-/* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(357);
-/* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(362);
-/* harmony import */ var _utils_yarn_lock__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(359);
-/* harmony import */ var _utils_validate_dependencies__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(363);
+/* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(358);
+/* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(363);
+/* harmony import */ var _utils_yarn_lock__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(360);
+/* harmony import */ var _utils_validate_dependencies__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(364);
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
@@ -32193,7 +32193,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(314);
/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_2__);
-/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(349);
+/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(350);
/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(246);
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
@@ -32289,13 +32289,13 @@ const childProcess = __webpack_require__(315);
const crossSpawn = __webpack_require__(316);
const stripFinalNewline = __webpack_require__(329);
const npmRunPath = __webpack_require__(330);
-const onetime = __webpack_require__(331);
-const makeError = __webpack_require__(333);
-const normalizeStdio = __webpack_require__(338);
-const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(339);
-const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(340);
-const {mergePromise, getSpawnedPromise} = __webpack_require__(347);
-const {joinCommand, parseCommand} = __webpack_require__(348);
+const onetime = __webpack_require__(332);
+const makeError = __webpack_require__(334);
+const normalizeStdio = __webpack_require__(339);
+const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(340);
+const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(341);
+const {mergePromise, getSpawnedPromise} = __webpack_require__(348);
+const {joinCommand, parseCommand} = __webpack_require__(349);
const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100;
@@ -33274,7 +33274,7 @@ module.exports = input => {
"use strict";
const path = __webpack_require__(4);
-const pathKey = __webpack_require__(323);
+const pathKey = __webpack_require__(331);
const npmRunPath = options => {
options = {
@@ -33327,7 +33327,30 @@ module.exports.env = options => {
"use strict";
-const mimicFn = __webpack_require__(332);
+
+const pathKey = (options = {}) => {
+ const environment = options.env || process.env;
+ const platform = options.platform || process.platform;
+
+ if (platform !== 'win32') {
+ return 'PATH';
+ }
+
+ return Object.keys(environment).find(key => key.toUpperCase() === 'PATH') || 'Path';
+};
+
+module.exports = pathKey;
+// TODO: Remove this for the next major release
+module.exports.default = pathKey;
+
+
+/***/ }),
+/* 332 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+const mimicFn = __webpack_require__(333);
const calledFunctions = new WeakMap();
@@ -33379,7 +33402,7 @@ module.exports.callCount = fn => {
/***/ }),
-/* 332 */
+/* 333 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -33399,12 +33422,12 @@ module.exports.default = mimicFn;
/***/ }),
-/* 333 */
+/* 334 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const {signalsByName} = __webpack_require__(334);
+const {signalsByName} = __webpack_require__(335);
const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => {
if (timedOut) {
@@ -33492,14 +33515,14 @@ module.exports = makeError;
/***/ }),
-/* 334 */
+/* 335 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports,"__esModule",{value:true});exports.signalsByNumber=exports.signalsByName=void 0;var _os=__webpack_require__(121);
-var _signals=__webpack_require__(335);
-var _realtime=__webpack_require__(337);
+var _signals=__webpack_require__(336);
+var _realtime=__webpack_require__(338);
@@ -33569,14 +33592,14 @@ const signalsByNumber=getSignalsByNumber();exports.signalsByNumber=signalsByNumb
//# sourceMappingURL=main.js.map
/***/ }),
-/* 335 */
+/* 336 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports,"__esModule",{value:true});exports.getSignals=void 0;var _os=__webpack_require__(121);
-var _core=__webpack_require__(336);
-var _realtime=__webpack_require__(337);
+var _core=__webpack_require__(337);
+var _realtime=__webpack_require__(338);
@@ -33610,7 +33633,7 @@ return{name,number,description,supported,action,forced,standard};
//# sourceMappingURL=signals.js.map
/***/ }),
-/* 336 */
+/* 337 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -33889,7 +33912,7 @@ standard:"other"}];exports.SIGNALS=SIGNALS;
//# sourceMappingURL=core.js.map
/***/ }),
-/* 337 */
+/* 338 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -33914,7 +33937,7 @@ const SIGRTMAX=64;exports.SIGRTMAX=SIGRTMAX;
//# sourceMappingURL=realtime.js.map
/***/ }),
-/* 338 */
+/* 339 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -33973,7 +33996,7 @@ module.exports.node = opts => {
/***/ }),
-/* 339 */
+/* 340 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -34092,14 +34115,14 @@ module.exports = {
/***/ }),
-/* 340 */
+/* 341 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const isStream = __webpack_require__(341);
-const getStream = __webpack_require__(342);
-const mergeStream = __webpack_require__(346);
+const isStream = __webpack_require__(342);
+const getStream = __webpack_require__(343);
+const mergeStream = __webpack_require__(347);
// `input` option
const handleInput = (spawned, input) => {
@@ -34196,7 +34219,7 @@ module.exports = {
/***/ }),
-/* 341 */
+/* 342 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -34232,13 +34255,13 @@ module.exports = isStream;
/***/ }),
-/* 342 */
+/* 343 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const pump = __webpack_require__(343);
-const bufferStream = __webpack_require__(345);
+const pump = __webpack_require__(344);
+const bufferStream = __webpack_require__(346);
class MaxBufferError extends Error {
constructor() {
@@ -34297,11 +34320,11 @@ module.exports.MaxBufferError = MaxBufferError;
/***/ }),
-/* 343 */
+/* 344 */
/***/ (function(module, exports, __webpack_require__) {
var once = __webpack_require__(162)
-var eos = __webpack_require__(344)
+var eos = __webpack_require__(345)
var fs = __webpack_require__(134) // we only need fs to get the ReadStream and WriteStream prototypes
var noop = function () {}
@@ -34385,7 +34408,7 @@ module.exports = pump
/***/ }),
-/* 344 */
+/* 345 */
/***/ (function(module, exports, __webpack_require__) {
var once = __webpack_require__(162);
@@ -34485,7 +34508,7 @@ module.exports = eos;
/***/ }),
-/* 345 */
+/* 346 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -34544,7 +34567,7 @@ module.exports = options => {
/***/ }),
-/* 346 */
+/* 347 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -34592,7 +34615,7 @@ module.exports = function (/*streams...*/) {
/***/ }),
-/* 347 */
+/* 348 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -34645,7 +34668,7 @@ module.exports = {
/***/ }),
-/* 348 */
+/* 349 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -34690,7 +34713,7 @@ module.exports = {
/***/ }),
-/* 349 */
+/* 350 */
/***/ (function(module, exports, __webpack_require__) {
// Copyright IBM Corp. 2014,2018. All Rights Reserved.
@@ -34698,12 +34721,12 @@ module.exports = {
// This file is licensed under the Apache License 2.0.
// License text available at https://opensource.org/licenses/Apache-2.0
-module.exports = __webpack_require__(350);
-module.exports.cli = __webpack_require__(354);
+module.exports = __webpack_require__(351);
+module.exports.cli = __webpack_require__(355);
/***/ }),
-/* 350 */
+/* 351 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -34718,9 +34741,9 @@ var stream = __webpack_require__(138);
var util = __webpack_require__(112);
var fs = __webpack_require__(134);
-var through = __webpack_require__(351);
-var duplexer = __webpack_require__(352);
-var StringDecoder = __webpack_require__(353).StringDecoder;
+var through = __webpack_require__(352);
+var duplexer = __webpack_require__(353);
+var StringDecoder = __webpack_require__(354).StringDecoder;
module.exports = Logger;
@@ -34909,7 +34932,7 @@ function lineMerger(host) {
/***/ }),
-/* 351 */
+/* 352 */
/***/ (function(module, exports, __webpack_require__) {
var Stream = __webpack_require__(138)
@@ -35023,7 +35046,7 @@ function through (write, end, opts) {
/***/ }),
-/* 352 */
+/* 353 */
/***/ (function(module, exports, __webpack_require__) {
var Stream = __webpack_require__(138)
@@ -35116,13 +35139,13 @@ function duplex(writer, reader) {
/***/ }),
-/* 353 */
+/* 354 */
/***/ (function(module, exports) {
module.exports = require("string_decoder");
/***/ }),
-/* 354 */
+/* 355 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -35133,11 +35156,11 @@ module.exports = require("string_decoder");
-var minimist = __webpack_require__(355);
+var minimist = __webpack_require__(356);
var path = __webpack_require__(4);
-var Logger = __webpack_require__(350);
-var pkg = __webpack_require__(356);
+var Logger = __webpack_require__(351);
+var pkg = __webpack_require__(357);
module.exports = cli;
@@ -35191,7 +35214,7 @@ function usage($0, p) {
/***/ }),
-/* 355 */
+/* 356 */
/***/ (function(module, exports) {
module.exports = function (args, opts) {
@@ -35442,13 +35465,13 @@ function isNumber (x) {
/***/ }),
-/* 356 */
+/* 357 */
/***/ (function(module) {
module.exports = JSON.parse("{\"name\":\"strong-log-transformer\",\"version\":\"2.1.0\",\"description\":\"Stream transformer that prefixes lines with timestamps and other things.\",\"author\":\"Ryan Graham \",\"license\":\"Apache-2.0\",\"repository\":{\"type\":\"git\",\"url\":\"git://github.com/strongloop/strong-log-transformer\"},\"keywords\":[\"logging\",\"streams\"],\"bugs\":{\"url\":\"https://github.com/strongloop/strong-log-transformer/issues\"},\"homepage\":\"https://github.com/strongloop/strong-log-transformer\",\"directories\":{\"test\":\"test\"},\"bin\":{\"sl-log-transformer\":\"bin/sl-log-transformer.js\"},\"main\":\"index.js\",\"scripts\":{\"test\":\"tap --100 test/test-*\"},\"dependencies\":{\"duplexer\":\"^0.1.1\",\"minimist\":\"^1.2.0\",\"through\":\"^2.3.4\"},\"devDependencies\":{\"tap\":\"^12.0.1\"},\"engines\":{\"node\":\">=4\"}}");
/***/ }),
-/* 357 */
+/* 358 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -35456,13 +35479,13 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getAllChecksums", function() { return getAllChecksums; });
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(134);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(358);
+/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(359);
/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(crypto__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(112);
/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(314);
/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_3__);
-/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(359);
+/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(360);
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
@@ -35661,20 +35684,20 @@ async function getAllChecksums(kbn, log, yarnLock) {
}
/***/ }),
-/* 358 */
+/* 359 */
/***/ (function(module, exports) {
module.exports = require("crypto");
/***/ }),
-/* 359 */
+/* 360 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "readYarnLock", function() { return readYarnLock; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resolveDepsForProject", function() { return resolveDepsForProject; });
-/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(360);
+/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(361);
/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(131);
/*
@@ -35787,7 +35810,7 @@ function resolveDepsForProject({
}
/***/ }),
-/* 360 */
+/* 361 */
/***/ (function(module, exports, __webpack_require__) {
module.exports =
@@ -37346,7 +37369,7 @@ module.exports = invariant;
/* 9 */
/***/ (function(module, exports) {
-module.exports = __webpack_require__(358);
+module.exports = __webpack_require__(359);
/***/ }),
/* 10 */,
@@ -39670,7 +39693,7 @@ function onceStrict (fn) {
/* 63 */
/***/ (function(module, exports) {
-module.exports = __webpack_require__(361);
+module.exports = __webpack_require__(362);
/***/ }),
/* 64 */,
@@ -46065,13 +46088,13 @@ module.exports = process && support(supportLevel);
/******/ ]);
/***/ }),
-/* 361 */
+/* 362 */
/***/ (function(module, exports) {
module.exports = require("buffer");
/***/ }),
-/* 362 */
+/* 363 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -46168,13 +46191,13 @@ class BootstrapCacheFile {
}
/***/ }),
-/* 363 */
+/* 364 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "validateDependencies", function() { return validateDependencies; });
-/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(360);
+/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(361);
/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_1__);
@@ -46185,7 +46208,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131);
/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246);
/* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251);
-/* harmony import */ var _projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(364);
+/* harmony import */ var _projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(365);
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
@@ -46377,7 +46400,7 @@ function getDevOnlyProductionDepsTree(kbn, projectName) {
}
/***/ }),
-/* 364 */
+/* 365 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -46530,7 +46553,7 @@ function addProjectToTree(tree, pathParts, project) {
}
/***/ }),
-/* 365 */
+/* 366 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -46538,7 +46561,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; });
/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(143);
/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(366);
+/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(367);
/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__);
@@ -46638,20 +46661,20 @@ const CleanCommand = {
};
/***/ }),
-/* 366 */
+/* 367 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const readline = __webpack_require__(367);
-const chalk = __webpack_require__(368);
-const cliCursor = __webpack_require__(375);
-const cliSpinners = __webpack_require__(379);
-const logSymbols = __webpack_require__(381);
-const stripAnsi = __webpack_require__(390);
-const wcwidth = __webpack_require__(392);
-const isInteractive = __webpack_require__(396);
-const MuteStream = __webpack_require__(397);
+const readline = __webpack_require__(368);
+const chalk = __webpack_require__(369);
+const cliCursor = __webpack_require__(376);
+const cliSpinners = __webpack_require__(378);
+const logSymbols = __webpack_require__(380);
+const stripAnsi = __webpack_require__(389);
+const wcwidth = __webpack_require__(391);
+const isInteractive = __webpack_require__(395);
+const MuteStream = __webpack_require__(396);
const TEXT = Symbol('text');
const PREFIX_TEXT = Symbol('prefixText');
@@ -47004,23 +47027,23 @@ module.exports.promise = (action, options) => {
/***/ }),
-/* 367 */
+/* 368 */
/***/ (function(module, exports) {
module.exports = require("readline");
/***/ }),
-/* 368 */
+/* 369 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const ansiStyles = __webpack_require__(369);
+const ansiStyles = __webpack_require__(370);
const {stdout: stdoutColor, stderr: stderrColor} = __webpack_require__(120);
const {
stringReplaceAll,
stringEncaseCRLFWithFirstIndex
-} = __webpack_require__(373);
+} = __webpack_require__(374);
// `supportsColor.level` → `ansiStyles.color[name]` mapping
const levelMapping = [
@@ -47221,7 +47244,7 @@ const chalkTag = (chalk, ...strings) => {
}
if (template === undefined) {
- template = __webpack_require__(374);
+ template = __webpack_require__(375);
}
return template(chalk, parts.join(''));
@@ -47250,7 +47273,7 @@ module.exports = chalk;
/***/ }),
-/* 369 */
+/* 370 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -47296,7 +47319,7 @@ const setLazyProperty = (object, property, get) => {
let colorConvert;
const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => {
if (colorConvert === undefined) {
- colorConvert = __webpack_require__(370);
+ colorConvert = __webpack_require__(371);
}
const offset = isBackground ? 10 : 0;
@@ -47421,11 +47444,11 @@ Object.defineProperty(module, 'exports', {
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module)))
/***/ }),
-/* 370 */
+/* 371 */
/***/ (function(module, exports, __webpack_require__) {
-const conversions = __webpack_require__(371);
-const route = __webpack_require__(372);
+const conversions = __webpack_require__(372);
+const route = __webpack_require__(373);
const convert = {};
@@ -47508,7 +47531,7 @@ module.exports = convert;
/***/ }),
-/* 371 */
+/* 372 */
/***/ (function(module, exports, __webpack_require__) {
/* MIT license */
@@ -48353,10 +48376,10 @@ convert.rgb.gray = function (rgb) {
/***/ }),
-/* 372 */
+/* 373 */
/***/ (function(module, exports, __webpack_require__) {
-const conversions = __webpack_require__(371);
+const conversions = __webpack_require__(372);
/*
This function routes a model to all other models.
@@ -48456,7 +48479,7 @@ module.exports = function (fromModel) {
/***/ }),
-/* 373 */
+/* 374 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -48502,7 +48525,7 @@ module.exports = {
/***/ }),
-/* 374 */
+/* 375 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -48643,12 +48666,12 @@ module.exports = (chalk, temporary) => {
/***/ }),
-/* 375 */
+/* 376 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const restoreCursor = __webpack_require__(376);
+const restoreCursor = __webpack_require__(377);
let isHidden = false;
@@ -48685,12 +48708,12 @@ exports.toggle = (force, writableStream) => {
/***/ }),
-/* 376 */
+/* 377 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const onetime = __webpack_require__(377);
+const onetime = __webpack_require__(332);
const signalExit = __webpack_require__(304);
module.exports = onetime(() => {
@@ -48700,63 +48723,6 @@ module.exports = onetime(() => {
});
-/***/ }),
-/* 377 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-const mimicFn = __webpack_require__(378);
-
-const calledFunctions = new WeakMap();
-
-const oneTime = (fn, options = {}) => {
- if (typeof fn !== 'function') {
- throw new TypeError('Expected a function');
- }
-
- let ret;
- let isCalled = false;
- let callCount = 0;
- const functionName = fn.displayName || fn.name || '';
-
- const onetime = function (...args) {
- calledFunctions.set(onetime, ++callCount);
-
- if (isCalled) {
- if (options.throw === true) {
- throw new Error(`Function \`${functionName}\` can only be called once`);
- }
-
- return ret;
- }
-
- isCalled = true;
- ret = fn.apply(this, args);
- fn = null;
-
- return ret;
- };
-
- mimicFn(onetime, fn);
- calledFunctions.set(onetime, callCount);
-
- return onetime;
-};
-
-module.exports = oneTime;
-// TODO: Remove this for the next major release
-module.exports.default = oneTime;
-
-module.exports.callCount = fn => {
- if (!calledFunctions.has(fn)) {
- throw new Error(`The given function \`${fn.name}\` is not wrapped by the \`onetime\` package`);
- }
-
- return calledFunctions.get(fn);
-};
-
-
/***/ }),
/* 378 */
/***/ (function(module, exports, __webpack_require__) {
@@ -48764,27 +48730,7 @@ module.exports.callCount = fn => {
"use strict";
-const mimicFn = (to, from) => {
- for (const prop of Reflect.ownKeys(from)) {
- Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop));
- }
-
- return to;
-};
-
-module.exports = mimicFn;
-// TODO: Remove this for the next major release
-module.exports.default = mimicFn;
-
-
-/***/ }),
-/* 379 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-const spinners = Object.assign({}, __webpack_require__(380));
+const spinners = Object.assign({}, __webpack_require__(379));
const spinnersList = Object.keys(spinners);
@@ -48802,18 +48748,18 @@ module.exports.default = spinners;
/***/ }),
-/* 380 */
+/* 379 */
/***/ (function(module) {
module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"dots8Bit\":{\"interval\":80,\"frames\":[\"⠀\",\"⠁\",\"⠂\",\"⠃\",\"⠄\",\"⠅\",\"⠆\",\"⠇\",\"⡀\",\"⡁\",\"⡂\",\"⡃\",\"⡄\",\"⡅\",\"⡆\",\"⡇\",\"⠈\",\"⠉\",\"⠊\",\"⠋\",\"⠌\",\"⠍\",\"⠎\",\"⠏\",\"⡈\",\"⡉\",\"⡊\",\"⡋\",\"⡌\",\"⡍\",\"⡎\",\"⡏\",\"⠐\",\"⠑\",\"⠒\",\"⠓\",\"⠔\",\"⠕\",\"⠖\",\"⠗\",\"⡐\",\"⡑\",\"⡒\",\"⡓\",\"⡔\",\"⡕\",\"⡖\",\"⡗\",\"⠘\",\"⠙\",\"⠚\",\"⠛\",\"⠜\",\"⠝\",\"⠞\",\"⠟\",\"⡘\",\"⡙\",\"⡚\",\"⡛\",\"⡜\",\"⡝\",\"⡞\",\"⡟\",\"⠠\",\"⠡\",\"⠢\",\"⠣\",\"⠤\",\"⠥\",\"⠦\",\"⠧\",\"⡠\",\"⡡\",\"⡢\",\"⡣\",\"⡤\",\"⡥\",\"⡦\",\"⡧\",\"⠨\",\"⠩\",\"⠪\",\"⠫\",\"⠬\",\"⠭\",\"⠮\",\"⠯\",\"⡨\",\"⡩\",\"⡪\",\"⡫\",\"⡬\",\"⡭\",\"⡮\",\"⡯\",\"⠰\",\"⠱\",\"⠲\",\"⠳\",\"⠴\",\"⠵\",\"⠶\",\"⠷\",\"⡰\",\"⡱\",\"⡲\",\"⡳\",\"⡴\",\"⡵\",\"⡶\",\"⡷\",\"⠸\",\"⠹\",\"⠺\",\"⠻\",\"⠼\",\"⠽\",\"⠾\",\"⠿\",\"⡸\",\"⡹\",\"⡺\",\"⡻\",\"⡼\",\"⡽\",\"⡾\",\"⡿\",\"⢀\",\"⢁\",\"⢂\",\"⢃\",\"⢄\",\"⢅\",\"⢆\",\"⢇\",\"⣀\",\"⣁\",\"⣂\",\"⣃\",\"⣄\",\"⣅\",\"⣆\",\"⣇\",\"⢈\",\"⢉\",\"⢊\",\"⢋\",\"⢌\",\"⢍\",\"⢎\",\"⢏\",\"⣈\",\"⣉\",\"⣊\",\"⣋\",\"⣌\",\"⣍\",\"⣎\",\"⣏\",\"⢐\",\"⢑\",\"⢒\",\"⢓\",\"⢔\",\"⢕\",\"⢖\",\"⢗\",\"⣐\",\"⣑\",\"⣒\",\"⣓\",\"⣔\",\"⣕\",\"⣖\",\"⣗\",\"⢘\",\"⢙\",\"⢚\",\"⢛\",\"⢜\",\"⢝\",\"⢞\",\"⢟\",\"⣘\",\"⣙\",\"⣚\",\"⣛\",\"⣜\",\"⣝\",\"⣞\",\"⣟\",\"⢠\",\"⢡\",\"⢢\",\"⢣\",\"⢤\",\"⢥\",\"⢦\",\"⢧\",\"⣠\",\"⣡\",\"⣢\",\"⣣\",\"⣤\",\"⣥\",\"⣦\",\"⣧\",\"⢨\",\"⢩\",\"⢪\",\"⢫\",\"⢬\",\"⢭\",\"⢮\",\"⢯\",\"⣨\",\"⣩\",\"⣪\",\"⣫\",\"⣬\",\"⣭\",\"⣮\",\"⣯\",\"⢰\",\"⢱\",\"⢲\",\"⢳\",\"⢴\",\"⢵\",\"⢶\",\"⢷\",\"⣰\",\"⣱\",\"⣲\",\"⣳\",\"⣴\",\"⣵\",\"⣶\",\"⣷\",\"⢸\",\"⢹\",\"⢺\",\"⢻\",\"⢼\",\"⢽\",\"⢾\",\"⢿\",\"⣸\",\"⣹\",\"⣺\",\"⣻\",\"⣼\",\"⣽\",\"⣾\",\"⣿\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕛 \",\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"material\":{\"interval\":17,\"frames\":[\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███████▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"██████████▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"█████████████▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁██████████████▁▁▁▁\",\"▁▁▁██████████████▁▁▁\",\"▁▁▁▁█████████████▁▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁▁█████████████▁▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁▁███████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁▁█████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]},\"grenade\":{\"interval\":80,\"frames\":[\"، \",\"′ \",\" ´ \",\" ‾ \",\" ⸌\",\" ⸊\",\" |\",\" ⁎\",\" ⁕\",\" ෴ \",\" ⁓\",\" \",\" \",\" \"]},\"point\":{\"interval\":125,\"frames\":[\"∙∙∙\",\"●∙∙\",\"∙●∙\",\"∙∙●\",\"∙∙∙\"]},\"layer\":{\"interval\":150,\"frames\":[\"-\",\"=\",\"≡\"]},\"betaWave\":{\"interval\":80,\"frames\":[\"ρββββββ\",\"βρβββββ\",\"ββρββββ\",\"βββρβββ\",\"ββββρββ\",\"βββββρβ\",\"ββββββρ\"]}}");
/***/ }),
-/* 381 */
+/* 380 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const chalk = __webpack_require__(382);
+const chalk = __webpack_require__(381);
const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color';
@@ -48835,16 +48781,16 @@ module.exports = isSupported ? main : fallbacks;
/***/ }),
-/* 382 */
+/* 381 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const escapeStringRegexp = __webpack_require__(265);
-const ansiStyles = __webpack_require__(383);
-const stdoutColor = __webpack_require__(388).stdout;
+const ansiStyles = __webpack_require__(382);
+const stdoutColor = __webpack_require__(387).stdout;
-const template = __webpack_require__(389);
+const template = __webpack_require__(388);
const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm');
@@ -49070,12 +49016,12 @@ module.exports.default = module.exports; // For TypeScript
/***/ }),
-/* 383 */
+/* 382 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function(module) {
-const colorConvert = __webpack_require__(384);
+const colorConvert = __webpack_require__(383);
const wrapAnsi16 = (fn, offset) => function () {
const code = fn.apply(colorConvert, arguments);
@@ -49243,11 +49189,11 @@ Object.defineProperty(module, 'exports', {
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module)))
/***/ }),
-/* 384 */
+/* 383 */
/***/ (function(module, exports, __webpack_require__) {
-var conversions = __webpack_require__(385);
-var route = __webpack_require__(387);
+var conversions = __webpack_require__(384);
+var route = __webpack_require__(386);
var convert = {};
@@ -49327,11 +49273,11 @@ module.exports = convert;
/***/ }),
-/* 385 */
+/* 384 */
/***/ (function(module, exports, __webpack_require__) {
/* MIT license */
-var cssKeywords = __webpack_require__(386);
+var cssKeywords = __webpack_require__(385);
// NOTE: conversions should only return primitive values (i.e. arrays, or
// values that give correct `typeof` results).
@@ -50201,7 +50147,7 @@ convert.rgb.gray = function (rgb) {
/***/ }),
-/* 386 */
+/* 385 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -50360,10 +50306,10 @@ module.exports = {
/***/ }),
-/* 387 */
+/* 386 */
/***/ (function(module, exports, __webpack_require__) {
-var conversions = __webpack_require__(385);
+var conversions = __webpack_require__(384);
/*
this function routes a model to all other models.
@@ -50463,7 +50409,7 @@ module.exports = function (fromModel) {
/***/ }),
-/* 388 */
+/* 387 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -50601,7 +50547,7 @@ module.exports = {
/***/ }),
-/* 389 */
+/* 388 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -50736,18 +50682,18 @@ module.exports = (chalk, tmp) => {
/***/ }),
-/* 390 */
+/* 389 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const ansiRegex = __webpack_require__(391);
+const ansiRegex = __webpack_require__(390);
module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string;
/***/ }),
-/* 391 */
+/* 390 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -50764,14 +50710,14 @@ module.exports = ({onlyFirst = false} = {}) => {
/***/ }),
-/* 392 */
+/* 391 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var defaults = __webpack_require__(393)
-var combining = __webpack_require__(395)
+var defaults = __webpack_require__(392)
+var combining = __webpack_require__(394)
var DEFAULTS = {
nul: 0,
@@ -50870,10 +50816,10 @@ function bisearch(ucs) {
/***/ }),
-/* 393 */
+/* 392 */
/***/ (function(module, exports, __webpack_require__) {
-var clone = __webpack_require__(394);
+var clone = __webpack_require__(393);
module.exports = function(options, defaults) {
options = options || {};
@@ -50888,7 +50834,7 @@ module.exports = function(options, defaults) {
};
/***/ }),
-/* 394 */
+/* 393 */
/***/ (function(module, exports, __webpack_require__) {
var clone = (function() {
@@ -51060,7 +51006,7 @@ if ( true && module.exports) {
/***/ }),
-/* 395 */
+/* 394 */
/***/ (function(module, exports) {
module.exports = [
@@ -51116,7 +51062,7 @@ module.exports = [
/***/ }),
-/* 396 */
+/* 395 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -51132,7 +51078,7 @@ module.exports = ({stream = process.stdout} = {}) => {
/***/ }),
-/* 397 */
+/* 396 */
/***/ (function(module, exports, __webpack_require__) {
var Stream = __webpack_require__(138)
@@ -51283,7 +51229,7 @@ MuteStream.prototype.close = proxy('close')
/***/ }),
-/* 398 */
+/* 397 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -51344,7 +51290,7 @@ const RunCommand = {
};
/***/ }),
-/* 399 */
+/* 398 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -51354,7 +51300,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246);
/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247);
/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248);
-/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(400);
+/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(399);
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
@@ -51439,14 +51385,14 @@ const WatchCommand = {
};
/***/ }),
-/* 400 */
+/* 399 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; });
/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8);
-/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(401);
+/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(400);
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
@@ -51513,141 +51459,141 @@ function waitUntilWatchIsReady(stream, opts = {}) {
}
/***/ }),
-/* 401 */
+/* 400 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
-/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(402);
+/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(401);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; });
-/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(403);
+/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(402);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; });
-/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(404);
+/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(403);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; });
-/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(405);
+/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(404);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__["bufferCount"]; });
-/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(406);
+/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(405);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; });
-/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(407);
+/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(406);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; });
-/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(408);
+/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(407);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; });
-/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(409);
+/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(408);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; });
-/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(410);
+/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(409);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; });
-/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(411);
+/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(410);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; });
-/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(412);
+/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(411);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; });
/* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(80);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; });
-/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(413);
+/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(412);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; });
-/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(414);
+/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(413);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; });
-/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(415);
+/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(414);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; });
-/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(416);
+/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(415);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; });
-/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(417);
+/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(416);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; });
-/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(418);
+/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(417);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; });
-/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(419);
+/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(418);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; });
-/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(421);
+/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(420);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; });
-/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(422);
+/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(421);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; });
-/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(423);
+/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(422);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; });
-/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(424);
+/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(423);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; });
-/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(425);
+/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(424);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; });
-/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(426);
+/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(425);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; });
-/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(429);
+/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(428);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; });
-/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(430);
+/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(429);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; });
-/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(431);
+/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(430);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; });
-/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(432);
+/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(431);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; });
-/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(433);
+/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(432);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; });
/* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(105);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; });
-/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(434);
+/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(433);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; });
-/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(435);
+/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(434);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; });
-/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(436);
+/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(435);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; });
-/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(437);
+/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(436);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; });
/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(31);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__["groupBy"]; });
-/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(438);
+/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(437);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; });
-/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(439);
+/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(438);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; });
-/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(440);
+/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(439);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; });
/* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(66);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; });
-/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(442);
+/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(441);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; });
-/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(443);
+/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(442);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; });
-/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(444);
+/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(443);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; });
-/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(447);
+/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(446);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; });
/* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(81);
@@ -51658,175 +51604,175 @@ __webpack_require__.r(__webpack_exports__);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["flatMap"]; });
-/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(448);
+/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(447);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; });
-/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(449);
+/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(448);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; });
-/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(450);
+/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(449);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "min", function() { return _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__["min"]; });
-/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(451);
+/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(450);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; });
/* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(41);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; });
-/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(452);
+/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(451);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; });
-/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(453);
+/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(452);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; });
-/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(454);
+/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(453);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; });
-/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(455);
+/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(454);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; });
-/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(456);
+/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(455);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; });
-/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(457);
+/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(456);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; });
-/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(458);
+/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(457);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; });
-/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(459);
+/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(458);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; });
-/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(460);
+/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(459);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; });
-/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(445);
+/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(444);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; });
-/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(461);
+/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(460);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; });
-/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(462);
+/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(461);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; });
-/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(463);
+/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(462);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; });
-/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(464);
+/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(463);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; });
/* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(30);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; });
-/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(465);
+/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(464);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; });
-/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(466);
+/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(465);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; });
-/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(446);
+/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(445);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; });
-/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(467);
+/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(466);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; });
-/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(468);
+/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(467);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; });
-/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(469);
+/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(468);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; });
-/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(470);
+/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(469);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; });
-/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(471);
+/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(470);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; });
-/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(472);
+/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(471);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; });
-/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(473);
+/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(472);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; });
-/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(474);
+/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(473);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; });
-/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(475);
+/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(474);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; });
-/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(476);
+/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(475);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; });
-/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(478);
+/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(477);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; });
-/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(479);
+/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(478);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__["switchMap"]; });
-/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(480);
+/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(479);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__["switchMapTo"]; });
-/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(428);
+/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(427);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "take", function() { return _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__["take"]; });
-/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(441);
+/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(440);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__["takeLast"]; });
-/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(481);
+/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(480);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__["takeUntil"]; });
-/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(482);
+/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(481);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__["takeWhile"]; });
-/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(483);
+/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(482);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__["tap"]; });
-/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(484);
+/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(483);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__["throttle"]; });
-/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(485);
+/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(484);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__["throttleTime"]; });
-/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(427);
+/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(426);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__["throwIfEmpty"]; });
-/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(486);
+/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(485);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__["timeInterval"]; });
-/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(487);
+/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(486);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__["timeout"]; });
-/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(488);
+/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(487);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__["timeoutWith"]; });
-/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(489);
+/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(488);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__["timestamp"]; });
-/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(490);
+/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(489);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__["toArray"]; });
-/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(491);
+/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(490);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "window", function() { return _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__["window"]; });
-/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(492);
+/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(491);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__["windowCount"]; });
-/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(493);
+/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(492);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__["windowTime"]; });
-/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(494);
+/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(493);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__["windowToggle"]; });
-/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(495);
+/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(494);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__["windowWhen"]; });
-/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(496);
+/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(495);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__["withLatestFrom"]; });
-/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(497);
+/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(496);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__["zip"]; });
-/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(498);
+/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(497);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__["zipAll"]; });
/** PURE_IMPORTS_START PURE_IMPORTS_END */
@@ -51937,7 +51883,7 @@ __webpack_require__.r(__webpack_exports__);
/***/ }),
-/* 402 */
+/* 401 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52016,14 +51962,14 @@ var AuditSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 403 */
+/* 402 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return auditTime; });
/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55);
-/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(402);
+/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(401);
/* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(108);
/** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */
@@ -52039,7 +51985,7 @@ function auditTime(duration, scheduler) {
/***/ }),
-/* 404 */
+/* 403 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52086,7 +52032,7 @@ var BufferSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 405 */
+/* 404 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52187,7 +52133,7 @@ var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 406 */
+/* 405 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52348,7 +52294,7 @@ function dispatchBufferClose(arg) {
/***/ }),
-/* 407 */
+/* 406 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52467,7 +52413,7 @@ var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 408 */
+/* 407 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52560,7 +52506,7 @@ var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 409 */
+/* 408 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52620,7 +52566,7 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 410 */
+/* 409 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52636,7 +52582,7 @@ function combineAll(project) {
/***/ }),
-/* 411 */
+/* 410 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52668,7 +52614,7 @@ function combineLatest() {
/***/ }),
-/* 412 */
+/* 411 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52688,7 +52634,7 @@ function concat() {
/***/ }),
-/* 413 */
+/* 412 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52704,13 +52650,13 @@ function concatMap(project, resultSelector) {
/***/ }),
-/* 414 */
+/* 413 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return concatMapTo; });
-/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(413);
+/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(412);
/** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */
function concatMapTo(innerObservable, resultSelector) {
@@ -52720,7 +52666,7 @@ function concatMapTo(innerObservable, resultSelector) {
/***/ }),
-/* 415 */
+/* 414 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52785,7 +52731,7 @@ var CountSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 416 */
+/* 415 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52870,7 +52816,7 @@ var DebounceSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 417 */
+/* 416 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52946,7 +52892,7 @@ function dispatchNext(subscriber) {
/***/ }),
-/* 418 */
+/* 417 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -52996,7 +52942,7 @@ var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 419 */
+/* 418 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53004,7 +52950,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return delay; });
/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12);
/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55);
-/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(420);
+/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(419);
/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11);
/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(42);
/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */
@@ -53103,7 +53049,7 @@ var DelayMessage = /*@__PURE__*/ (function () {
/***/ }),
-/* 420 */
+/* 419 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53117,7 +53063,7 @@ function isDate(value) {
/***/ }),
-/* 421 */
+/* 420 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53263,7 +53209,7 @@ var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 422 */
+/* 421 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53301,7 +53247,7 @@ var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 423 */
+/* 422 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53377,7 +53323,7 @@ var DistinctSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 424 */
+/* 423 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53448,13 +53394,13 @@ var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 425 */
+/* 424 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return distinctUntilKeyChanged; });
-/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(424);
+/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(423);
/** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */
function distinctUntilKeyChanged(key, compare) {
@@ -53464,7 +53410,7 @@ function distinctUntilKeyChanged(key, compare) {
/***/ }),
-/* 426 */
+/* 425 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53472,9 +53418,9 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return elementAt; });
/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(62);
/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105);
-/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(427);
-/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(418);
-/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(428);
+/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(426);
+/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(417);
+/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(427);
/** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */
@@ -53496,7 +53442,7 @@ function elementAt(index, defaultValue) {
/***/ }),
-/* 427 */
+/* 426 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53562,7 +53508,7 @@ function defaultErrorFactory() {
/***/ }),
-/* 428 */
+/* 427 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53624,7 +53570,7 @@ var TakeSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 429 */
+/* 428 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53646,7 +53592,7 @@ function endWith() {
/***/ }),
-/* 430 */
+/* 429 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53708,7 +53654,7 @@ var EverySubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 431 */
+/* 430 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53762,7 +53708,7 @@ var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 432 */
+/* 431 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53856,7 +53802,7 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 433 */
+/* 432 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -53968,7 +53914,7 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 434 */
+/* 433 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54006,7 +53952,7 @@ var FinallySubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 435 */
+/* 434 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54078,13 +54024,13 @@ var FindValueSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 436 */
+/* 435 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return findIndex; });
-/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(435);
+/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(434);
/** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */
function findIndex(predicate, thisArg) {
@@ -54094,7 +54040,7 @@ function findIndex(predicate, thisArg) {
/***/ }),
-/* 437 */
+/* 436 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54102,9 +54048,9 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "first", function() { return first; });
/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63);
/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105);
-/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(428);
-/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(418);
-/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(427);
+/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(427);
+/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(417);
+/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(426);
/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25);
/** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */
@@ -54121,7 +54067,7 @@ function first(predicate, defaultValue) {
/***/ }),
-/* 438 */
+/* 437 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54158,7 +54104,7 @@ var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 439 */
+/* 438 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54202,7 +54148,7 @@ var IsEmptySubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 440 */
+/* 439 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54210,9 +54156,9 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "last", function() { return last; });
/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63);
/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105);
-/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(441);
-/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(427);
-/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(418);
+/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(440);
+/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(426);
+/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(417);
/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25);
/** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */
@@ -54229,7 +54175,7 @@ function last(predicate, defaultValue) {
/***/ }),
-/* 441 */
+/* 440 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54306,7 +54252,7 @@ var TakeLastSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 442 */
+/* 441 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54345,7 +54291,7 @@ var MapToSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 443 */
+/* 442 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54395,13 +54341,13 @@ var MaterializeSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 444 */
+/* 443 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "max", function() { return max; });
-/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(445);
+/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(444);
/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */
function max(comparer) {
@@ -54414,15 +54360,15 @@ function max(comparer) {
/***/ }),
-/* 445 */
+/* 444 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return reduce; });
-/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(446);
-/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(441);
-/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(418);
+/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(445);
+/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(440);
+/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(417);
/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(24);
/** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */
@@ -54443,7 +54389,7 @@ function reduce(accumulator, seed) {
/***/ }),
-/* 446 */
+/* 445 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54525,7 +54471,7 @@ var ScanSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 447 */
+/* 446 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54545,7 +54491,7 @@ function merge() {
/***/ }),
-/* 448 */
+/* 447 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54570,7 +54516,7 @@ function mergeMapTo(innerObservable, resultSelector, concurrent) {
/***/ }),
-/* 449 */
+/* 448 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54679,13 +54625,13 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 450 */
+/* 449 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "min", function() { return min; });
-/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(445);
+/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(444);
/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */
function min(comparer) {
@@ -54698,7 +54644,7 @@ function min(comparer) {
/***/ }),
-/* 451 */
+/* 450 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54747,7 +54693,7 @@ var MulticastOperator = /*@__PURE__*/ (function () {
/***/ }),
-/* 452 */
+/* 451 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54837,7 +54783,7 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 453 */
+/* 452 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54885,7 +54831,7 @@ var PairwiseSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 454 */
+/* 453 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54908,7 +54854,7 @@ function partition(predicate, thisArg) {
/***/ }),
-/* 455 */
+/* 454 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -54948,14 +54894,14 @@ function plucker(props, length) {
/***/ }),
-/* 456 */
+/* 455 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return publish; });
/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(27);
-/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(451);
+/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450);
/** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */
@@ -54968,14 +54914,14 @@ function publish(selector) {
/***/ }),
-/* 457 */
+/* 456 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return publishBehavior; });
/* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(32);
-/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(451);
+/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450);
/** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */
@@ -54986,14 +54932,14 @@ function publishBehavior(value) {
/***/ }),
-/* 458 */
+/* 457 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return publishLast; });
/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(50);
-/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(451);
+/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450);
/** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */
@@ -55004,14 +54950,14 @@ function publishLast() {
/***/ }),
-/* 459 */
+/* 458 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return publishReplay; });
/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(33);
-/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(451);
+/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450);
/** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */
@@ -55027,7 +54973,7 @@ function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) {
/***/ }),
-/* 460 */
+/* 459 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55054,7 +55000,7 @@ function race() {
/***/ }),
-/* 461 */
+/* 460 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55119,7 +55065,7 @@ var RepeatSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 462 */
+/* 461 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55213,7 +55159,7 @@ var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 463 */
+/* 462 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55266,7 +55212,7 @@ var RetrySubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 464 */
+/* 463 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55352,7 +55298,7 @@ var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 465 */
+/* 464 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55407,7 +55353,7 @@ var SampleSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 466 */
+/* 465 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55467,7 +55413,7 @@ function dispatchNotification(state) {
/***/ }),
-/* 467 */
+/* 466 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55590,13 +55536,13 @@ var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 468 */
+/* 467 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "share", function() { return share; });
-/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(451);
+/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(450);
/* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30);
/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27);
/** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */
@@ -55613,7 +55559,7 @@ function share() {
/***/ }),
-/* 469 */
+/* 468 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55682,7 +55628,7 @@ function shareReplayOperator(_a) {
/***/ }),
-/* 470 */
+/* 469 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55762,7 +55708,7 @@ var SingleSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 471 */
+/* 470 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55804,7 +55750,7 @@ var SkipSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 472 */
+/* 471 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55866,7 +55812,7 @@ var SkipLastSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 473 */
+/* 472 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55923,7 +55869,7 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 474 */
+/* 473 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -55979,7 +55925,7 @@ var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 475 */
+/* 474 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56008,13 +55954,13 @@ function startWith() {
/***/ }),
-/* 476 */
+/* 475 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return subscribeOn; });
-/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(477);
+/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(476);
/** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */
function subscribeOn(scheduler, delay) {
@@ -56039,7 +55985,7 @@ var SubscribeOnOperator = /*@__PURE__*/ (function () {
/***/ }),
-/* 477 */
+/* 476 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56103,13 +56049,13 @@ var SubscribeOnObservable = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 478 */
+/* 477 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return switchAll; });
-/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(479);
+/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(478);
/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25);
/** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */
@@ -56121,7 +56067,7 @@ function switchAll() {
/***/ }),
-/* 479 */
+/* 478 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56209,13 +56155,13 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 480 */
+/* 479 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return switchMapTo; });
-/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(479);
+/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(478);
/** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */
function switchMapTo(innerObservable, resultSelector) {
@@ -56225,7 +56171,7 @@ function switchMapTo(innerObservable, resultSelector) {
/***/ }),
-/* 481 */
+/* 480 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56273,7 +56219,7 @@ var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 482 */
+/* 481 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56341,7 +56287,7 @@ var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 483 */
+/* 482 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56429,7 +56375,7 @@ var TapSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 484 */
+/* 483 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56531,7 +56477,7 @@ var ThrottleSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 485 */
+/* 484 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56540,7 +56486,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12);
/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55);
-/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(484);
+/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(483);
/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */
@@ -56629,7 +56575,7 @@ function dispatchNext(arg) {
/***/ }),
-/* 486 */
+/* 485 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56637,7 +56583,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return timeInterval; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeInterval", function() { return TimeInterval; });
/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55);
-/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(446);
+/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(445);
/* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(91);
/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(66);
/** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */
@@ -56673,7 +56619,7 @@ var TimeInterval = /*@__PURE__*/ (function () {
/***/ }),
-/* 487 */
+/* 486 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56681,7 +56627,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return timeout; });
/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55);
/* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(64);
-/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(488);
+/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(487);
/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(49);
/** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */
@@ -56698,7 +56644,7 @@ function timeout(due, scheduler) {
/***/ }),
-/* 488 */
+/* 487 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56706,7 +56652,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return timeoutWith; });
/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12);
/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55);
-/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(420);
+/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(419);
/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90);
/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_innerSubscribe PURE_IMPORTS_END */
@@ -56777,7 +56723,7 @@ var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 489 */
+/* 488 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56807,13 +56753,13 @@ var Timestamp = /*@__PURE__*/ (function () {
/***/ }),
-/* 490 */
+/* 489 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return toArray; });
-/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(445);
+/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(444);
/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */
function toArrayReducer(arr, item, index) {
@@ -56830,7 +56776,7 @@ function toArray() {
/***/ }),
-/* 491 */
+/* 490 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56908,7 +56854,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 492 */
+/* 491 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -56998,7 +56944,7 @@ var WindowCountSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 493 */
+/* 492 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -57168,7 +57114,7 @@ function dispatchWindowClose(state) {
/***/ }),
-/* 494 */
+/* 493 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -57311,7 +57257,7 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 495 */
+/* 494 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -57408,7 +57354,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 496 */
+/* 495 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -57503,7 +57449,7 @@ var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) {
/***/ }),
-/* 497 */
+/* 496 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -57525,7 +57471,7 @@ function zip() {
/***/ }),
-/* 498 */
+/* 497 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -57541,7 +57487,7 @@ function zipAll(project) {
/***/ }),
-/* 499 */
+/* 498 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -57550,8 +57496,8 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249);
/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246);
/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248);
-/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(364);
-/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(500);
+/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(365);
+/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(499);
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
@@ -57633,7 +57579,7 @@ function toArray(value) {
}
/***/ }),
-/* 500 */
+/* 499 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -57641,13 +57587,13 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; });
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(501);
+/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(500);
/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(239);
/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_2__);
-/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(359);
+/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(360);
/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248);
-/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(505);
+/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(504);
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
@@ -57809,15 +57755,15 @@ class Kibana {
}
/***/ }),
-/* 501 */
+/* 500 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const minimatch = __webpack_require__(150);
-const arrayUnion = __webpack_require__(502);
-const arrayDiffer = __webpack_require__(503);
-const arrify = __webpack_require__(504);
+const arrayUnion = __webpack_require__(501);
+const arrayDiffer = __webpack_require__(502);
+const arrify = __webpack_require__(503);
module.exports = (list, patterns, options = {}) => {
list = arrify(list);
@@ -57841,7 +57787,7 @@ module.exports = (list, patterns, options = {}) => {
/***/ }),
-/* 502 */
+/* 501 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -57853,7 +57799,7 @@ module.exports = (...arguments_) => {
/***/ }),
-/* 503 */
+/* 502 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -57868,7 +57814,7 @@ module.exports = arrayDiffer;
/***/ }),
-/* 504 */
+/* 503 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -57898,7 +57844,7 @@ module.exports = arrify;
/***/ }),
-/* 505 */
+/* 504 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -57968,12 +57914,12 @@ function getProjectPaths({
}
/***/ }),
-/* 506 */
+/* 505 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
-/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(507);
+/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(506);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; });
/*
@@ -57997,19 +57943,19 @@ __webpack_require__.r(__webpack_exports__);
/***/ }),
-/* 507 */
+/* 506 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return buildProductionProjects; });
-/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(508);
+/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(507);
/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143);
/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__);
-/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(505);
+/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(504);
/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131);
/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246);
/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251);
@@ -58146,7 +58092,7 @@ async function copyToBuild(project, kibanaRoot, buildRoot) {
}
/***/ }),
-/* 508 */
+/* 507 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -58154,14 +58100,14 @@ async function copyToBuild(project, kibanaRoot, buildRoot) {
const EventEmitter = __webpack_require__(156);
const path = __webpack_require__(4);
const os = __webpack_require__(121);
-const pMap = __webpack_require__(509);
-const arrify = __webpack_require__(510);
-const globby = __webpack_require__(511);
-const hasGlob = __webpack_require__(707);
-const cpFile = __webpack_require__(709);
-const junk = __webpack_require__(719);
-const pFilter = __webpack_require__(720);
-const CpyError = __webpack_require__(722);
+const pMap = __webpack_require__(508);
+const arrify = __webpack_require__(503);
+const globby = __webpack_require__(509);
+const hasGlob = __webpack_require__(705);
+const cpFile = __webpack_require__(707);
+const junk = __webpack_require__(717);
+const pFilter = __webpack_require__(718);
+const CpyError = __webpack_require__(720);
const defaultOptions = {
ignoreJunk: true
@@ -58312,7 +58258,7 @@ module.exports = (source, destination, {
/***/ }),
-/* 509 */
+/* 508 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -58400,47 +58346,17 @@ module.exports = async (
/***/ }),
-/* 510 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-const arrify = value => {
- if (value === null || value === undefined) {
- return [];
- }
-
- if (Array.isArray(value)) {
- return value;
- }
-
- if (typeof value === 'string') {
- return [value];
- }
-
- if (typeof value[Symbol.iterator] === 'function') {
- return [...value];
- }
-
- return [value];
-};
-
-module.exports = arrify;
-
-
-/***/ }),
-/* 511 */
+/* 509 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const fs = __webpack_require__(134);
-const arrayUnion = __webpack_require__(512);
+const arrayUnion = __webpack_require__(510);
const glob = __webpack_require__(147);
-const fastGlob = __webpack_require__(514);
-const dirGlob = __webpack_require__(700);
-const gitignore = __webpack_require__(703);
+const fastGlob = __webpack_require__(512);
+const dirGlob = __webpack_require__(698);
+const gitignore = __webpack_require__(701);
const DEFAULT_FILTER = () => false;
@@ -58585,12 +58501,12 @@ module.exports.gitignore = gitignore;
/***/ }),
-/* 512 */
+/* 510 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var arrayUniq = __webpack_require__(513);
+var arrayUniq = __webpack_require__(511);
module.exports = function () {
return arrayUniq([].concat.apply([], arguments));
@@ -58598,7 +58514,7 @@ module.exports = function () {
/***/ }),
-/* 513 */
+/* 511 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -58667,10 +58583,10 @@ if ('Set' in global) {
/***/ }),
-/* 514 */
+/* 512 */
/***/ (function(module, exports, __webpack_require__) {
-const pkg = __webpack_require__(515);
+const pkg = __webpack_require__(513);
module.exports = pkg.async;
module.exports.default = pkg.async;
@@ -58683,19 +58599,19 @@ module.exports.generateTasks = pkg.generateTasks;
/***/ }),
-/* 515 */
+/* 513 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-var optionsManager = __webpack_require__(516);
-var taskManager = __webpack_require__(517);
-var reader_async_1 = __webpack_require__(671);
-var reader_stream_1 = __webpack_require__(695);
-var reader_sync_1 = __webpack_require__(696);
-var arrayUtils = __webpack_require__(698);
-var streamUtils = __webpack_require__(699);
+var optionsManager = __webpack_require__(514);
+var taskManager = __webpack_require__(515);
+var reader_async_1 = __webpack_require__(669);
+var reader_stream_1 = __webpack_require__(693);
+var reader_sync_1 = __webpack_require__(694);
+var arrayUtils = __webpack_require__(696);
+var streamUtils = __webpack_require__(697);
/**
* Synchronous API.
*/
@@ -58761,7 +58677,7 @@ function isString(source) {
/***/ }),
-/* 516 */
+/* 514 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -58799,13 +58715,13 @@ exports.prepare = prepare;
/***/ }),
-/* 517 */
+/* 515 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-var patternUtils = __webpack_require__(518);
+var patternUtils = __webpack_require__(516);
/**
* Generate tasks based on parent directory of each pattern.
*/
@@ -58896,16 +58812,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask;
/***/ }),
-/* 518 */
+/* 516 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var path = __webpack_require__(4);
-var globParent = __webpack_require__(519);
+var globParent = __webpack_require__(517);
var isGlob = __webpack_require__(172);
-var micromatch = __webpack_require__(522);
+var micromatch = __webpack_require__(520);
var GLOBSTAR = '**';
/**
* Return true for static pattern.
@@ -59051,15 +58967,15 @@ exports.matchAny = matchAny;
/***/ }),
-/* 519 */
+/* 517 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var path = __webpack_require__(4);
-var isglob = __webpack_require__(520);
-var pathDirname = __webpack_require__(521);
+var isglob = __webpack_require__(518);
+var pathDirname = __webpack_require__(519);
var isWin32 = __webpack_require__(121).platform() === 'win32';
module.exports = function globParent(str) {
@@ -59082,7 +58998,7 @@ module.exports = function globParent(str) {
/***/ }),
-/* 520 */
+/* 518 */
/***/ (function(module, exports, __webpack_require__) {
/*!
@@ -59113,7 +59029,7 @@ module.exports = function isGlob(str) {
/***/ }),
-/* 521 */
+/* 519 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -59263,7 +59179,7 @@ module.exports.win32 = win32;
/***/ }),
-/* 522 */
+/* 520 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -59274,18 +59190,18 @@ module.exports.win32 = win32;
*/
var util = __webpack_require__(112);
-var braces = __webpack_require__(523);
-var toRegex = __webpack_require__(524);
-var extend = __webpack_require__(637);
+var braces = __webpack_require__(521);
+var toRegex = __webpack_require__(522);
+var extend = __webpack_require__(635);
/**
* Local dependencies
*/
-var compilers = __webpack_require__(639);
-var parsers = __webpack_require__(666);
-var cache = __webpack_require__(667);
-var utils = __webpack_require__(668);
+var compilers = __webpack_require__(637);
+var parsers = __webpack_require__(664);
+var cache = __webpack_require__(665);
+var utils = __webpack_require__(666);
var MAX_LENGTH = 1024 * 64;
/**
@@ -60147,7 +60063,7 @@ module.exports = micromatch;
/***/ }),
-/* 523 */
+/* 521 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -60157,18 +60073,18 @@ module.exports = micromatch;
* Module dependencies
*/
-var toRegex = __webpack_require__(524);
-var unique = __webpack_require__(546);
-var extend = __webpack_require__(547);
+var toRegex = __webpack_require__(522);
+var unique = __webpack_require__(544);
+var extend = __webpack_require__(545);
/**
* Local dependencies
*/
-var compilers = __webpack_require__(549);
-var parsers = __webpack_require__(562);
-var Braces = __webpack_require__(566);
-var utils = __webpack_require__(550);
+var compilers = __webpack_require__(547);
+var parsers = __webpack_require__(560);
+var Braces = __webpack_require__(564);
+var utils = __webpack_require__(548);
var MAX_LENGTH = 1024 * 64;
var cache = {};
@@ -60472,16 +60388,16 @@ module.exports = braces;
/***/ }),
-/* 524 */
+/* 522 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var safe = __webpack_require__(525);
-var define = __webpack_require__(531);
-var extend = __webpack_require__(539);
-var not = __webpack_require__(543);
+var safe = __webpack_require__(523);
+var define = __webpack_require__(529);
+var extend = __webpack_require__(537);
+var not = __webpack_require__(541);
var MAX_LENGTH = 1024 * 64;
/**
@@ -60634,10 +60550,10 @@ module.exports.makeRe = makeRe;
/***/ }),
-/* 525 */
+/* 523 */
/***/ (function(module, exports, __webpack_require__) {
-var parse = __webpack_require__(526);
+var parse = __webpack_require__(524);
var types = parse.types;
module.exports = function (re, opts) {
@@ -60683,13 +60599,13 @@ function isRegExp (x) {
/***/ }),
-/* 526 */
+/* 524 */
/***/ (function(module, exports, __webpack_require__) {
-var util = __webpack_require__(527);
-var types = __webpack_require__(528);
-var sets = __webpack_require__(529);
-var positions = __webpack_require__(530);
+var util = __webpack_require__(525);
+var types = __webpack_require__(526);
+var sets = __webpack_require__(527);
+var positions = __webpack_require__(528);
module.exports = function(regexpStr) {
@@ -60971,11 +60887,11 @@ module.exports.types = types;
/***/ }),
-/* 527 */
+/* 525 */
/***/ (function(module, exports, __webpack_require__) {
-var types = __webpack_require__(528);
-var sets = __webpack_require__(529);
+var types = __webpack_require__(526);
+var sets = __webpack_require__(527);
// All of these are private and only used by randexp.
@@ -61088,7 +61004,7 @@ exports.error = function(regexp, msg) {
/***/ }),
-/* 528 */
+/* 526 */
/***/ (function(module, exports) {
module.exports = {
@@ -61104,10 +61020,10 @@ module.exports = {
/***/ }),
-/* 529 */
+/* 527 */
/***/ (function(module, exports, __webpack_require__) {
-var types = __webpack_require__(528);
+var types = __webpack_require__(526);
var INTS = function() {
return [{ type: types.RANGE , from: 48, to: 57 }];
@@ -61192,10 +61108,10 @@ exports.anyChar = function() {
/***/ }),
-/* 530 */
+/* 528 */
/***/ (function(module, exports, __webpack_require__) {
-var types = __webpack_require__(528);
+var types = __webpack_require__(526);
exports.wordBoundary = function() {
return { type: types.POSITION, value: 'b' };
@@ -61215,7 +61131,7 @@ exports.end = function() {
/***/ }),
-/* 531 */
+/* 529 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -61228,8 +61144,8 @@ exports.end = function() {
-var isobject = __webpack_require__(532);
-var isDescriptor = __webpack_require__(533);
+var isobject = __webpack_require__(530);
+var isDescriptor = __webpack_require__(531);
var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty)
? Reflect.defineProperty
: Object.defineProperty;
@@ -61260,7 +61176,7 @@ module.exports = function defineProperty(obj, key, val) {
/***/ }),
-/* 532 */
+/* 530 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -61279,7 +61195,7 @@ module.exports = function isObject(val) {
/***/ }),
-/* 533 */
+/* 531 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -61292,9 +61208,9 @@ module.exports = function isObject(val) {
-var typeOf = __webpack_require__(534);
-var isAccessor = __webpack_require__(535);
-var isData = __webpack_require__(537);
+var typeOf = __webpack_require__(532);
+var isAccessor = __webpack_require__(533);
+var isData = __webpack_require__(535);
module.exports = function isDescriptor(obj, key) {
if (typeOf(obj) !== 'object') {
@@ -61308,7 +61224,7 @@ module.exports = function isDescriptor(obj, key) {
/***/ }),
-/* 534 */
+/* 532 */
/***/ (function(module, exports) {
var toString = Object.prototype.toString;
@@ -61443,7 +61359,7 @@ function isBuffer(val) {
/***/ }),
-/* 535 */
+/* 533 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -61456,7 +61372,7 @@ function isBuffer(val) {
-var typeOf = __webpack_require__(536);
+var typeOf = __webpack_require__(534);
// accessor descriptor properties
var accessor = {
@@ -61519,7 +61435,7 @@ module.exports = isAccessorDescriptor;
/***/ }),
-/* 536 */
+/* 534 */
/***/ (function(module, exports) {
var toString = Object.prototype.toString;
@@ -61654,7 +61570,7 @@ function isBuffer(val) {
/***/ }),
-/* 537 */
+/* 535 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -61667,7 +61583,7 @@ function isBuffer(val) {
-var typeOf = __webpack_require__(538);
+var typeOf = __webpack_require__(536);
module.exports = function isDataDescriptor(obj, prop) {
// data descriptor properties
@@ -61710,7 +61626,7 @@ module.exports = function isDataDescriptor(obj, prop) {
/***/ }),
-/* 538 */
+/* 536 */
/***/ (function(module, exports) {
var toString = Object.prototype.toString;
@@ -61845,14 +61761,14 @@ function isBuffer(val) {
/***/ }),
-/* 539 */
+/* 537 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var isExtendable = __webpack_require__(540);
-var assignSymbols = __webpack_require__(542);
+var isExtendable = __webpack_require__(538);
+var assignSymbols = __webpack_require__(540);
module.exports = Object.assign || function(obj/*, objects*/) {
if (obj === null || typeof obj === 'undefined') {
@@ -61912,7 +61828,7 @@ function isEnum(obj, key) {
/***/ }),
-/* 540 */
+/* 538 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -61925,7 +61841,7 @@ function isEnum(obj, key) {
-var isPlainObject = __webpack_require__(541);
+var isPlainObject = __webpack_require__(539);
module.exports = function isExtendable(val) {
return isPlainObject(val) || typeof val === 'function' || Array.isArray(val);
@@ -61933,7 +61849,7 @@ module.exports = function isExtendable(val) {
/***/ }),
-/* 541 */
+/* 539 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -61946,7 +61862,7 @@ module.exports = function isExtendable(val) {
-var isObject = __webpack_require__(532);
+var isObject = __webpack_require__(530);
function isObjectObject(o) {
return isObject(o) === true
@@ -61977,7 +61893,7 @@ module.exports = function isPlainObject(o) {
/***/ }),
-/* 542 */
+/* 540 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -62024,14 +61940,14 @@ module.exports = function(receiver, objects) {
/***/ }),
-/* 543 */
+/* 541 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var extend = __webpack_require__(544);
-var safe = __webpack_require__(525);
+var extend = __webpack_require__(542);
+var safe = __webpack_require__(523);
/**
* The main export is a function that takes a `pattern` string and an `options` object.
@@ -62103,14 +62019,14 @@ module.exports = toRegex;
/***/ }),
-/* 544 */
+/* 542 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var isExtendable = __webpack_require__(545);
-var assignSymbols = __webpack_require__(542);
+var isExtendable = __webpack_require__(543);
+var assignSymbols = __webpack_require__(540);
module.exports = Object.assign || function(obj/*, objects*/) {
if (obj === null || typeof obj === 'undefined') {
@@ -62170,7 +62086,7 @@ function isEnum(obj, key) {
/***/ }),
-/* 545 */
+/* 543 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -62183,7 +62099,7 @@ function isEnum(obj, key) {
-var isPlainObject = __webpack_require__(541);
+var isPlainObject = __webpack_require__(539);
module.exports = function isExtendable(val) {
return isPlainObject(val) || typeof val === 'function' || Array.isArray(val);
@@ -62191,7 +62107,7 @@ module.exports = function isExtendable(val) {
/***/ }),
-/* 546 */
+/* 544 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -62241,13 +62157,13 @@ module.exports.immutable = function uniqueImmutable(arr) {
/***/ }),
-/* 547 */
+/* 545 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var isObject = __webpack_require__(548);
+var isObject = __webpack_require__(546);
module.exports = function extend(o/*, objects*/) {
if (!isObject(o)) { o = {}; }
@@ -62281,7 +62197,7 @@ function hasOwn(obj, key) {
/***/ }),
-/* 548 */
+/* 546 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -62301,13 +62217,13 @@ module.exports = function isExtendable(val) {
/***/ }),
-/* 549 */
+/* 547 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var utils = __webpack_require__(550);
+var utils = __webpack_require__(548);
module.exports = function(braces, options) {
braces.compiler
@@ -62590,25 +62506,25 @@ function hasQueue(node) {
/***/ }),
-/* 550 */
+/* 548 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var splitString = __webpack_require__(551);
+var splitString = __webpack_require__(549);
var utils = module.exports;
/**
* Module dependencies
*/
-utils.extend = __webpack_require__(547);
-utils.flatten = __webpack_require__(554);
-utils.isObject = __webpack_require__(532);
-utils.fillRange = __webpack_require__(555);
-utils.repeat = __webpack_require__(561);
-utils.unique = __webpack_require__(546);
+utils.extend = __webpack_require__(545);
+utils.flatten = __webpack_require__(552);
+utils.isObject = __webpack_require__(530);
+utils.fillRange = __webpack_require__(553);
+utils.repeat = __webpack_require__(559);
+utils.unique = __webpack_require__(544);
utils.define = function(obj, key, val) {
Object.defineProperty(obj, key, {
@@ -62940,7 +62856,7 @@ utils.escapeRegex = function(str) {
/***/ }),
-/* 551 */
+/* 549 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -62953,7 +62869,7 @@ utils.escapeRegex = function(str) {
-var extend = __webpack_require__(552);
+var extend = __webpack_require__(550);
module.exports = function(str, options, fn) {
if (typeof str !== 'string') {
@@ -63118,14 +63034,14 @@ function keepEscaping(opts, str, idx) {
/***/ }),
-/* 552 */
+/* 550 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var isExtendable = __webpack_require__(553);
-var assignSymbols = __webpack_require__(542);
+var isExtendable = __webpack_require__(551);
+var assignSymbols = __webpack_require__(540);
module.exports = Object.assign || function(obj/*, objects*/) {
if (obj === null || typeof obj === 'undefined') {
@@ -63185,7 +63101,7 @@ function isEnum(obj, key) {
/***/ }),
-/* 553 */
+/* 551 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -63198,7 +63114,7 @@ function isEnum(obj, key) {
-var isPlainObject = __webpack_require__(541);
+var isPlainObject = __webpack_require__(539);
module.exports = function isExtendable(val) {
return isPlainObject(val) || typeof val === 'function' || Array.isArray(val);
@@ -63206,7 +63122,7 @@ module.exports = function isExtendable(val) {
/***/ }),
-/* 554 */
+/* 552 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -63235,7 +63151,7 @@ function flat(arr, res) {
/***/ }),
-/* 555 */
+/* 553 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -63249,10 +63165,10 @@ function flat(arr, res) {
var util = __webpack_require__(112);
-var isNumber = __webpack_require__(556);
-var extend = __webpack_require__(547);
-var repeat = __webpack_require__(559);
-var toRegex = __webpack_require__(560);
+var isNumber = __webpack_require__(554);
+var extend = __webpack_require__(545);
+var repeat = __webpack_require__(557);
+var toRegex = __webpack_require__(558);
/**
* Return a range of numbers or letters.
@@ -63450,7 +63366,7 @@ module.exports = fillRange;
/***/ }),
-/* 556 */
+/* 554 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -63463,7 +63379,7 @@ module.exports = fillRange;
-var typeOf = __webpack_require__(557);
+var typeOf = __webpack_require__(555);
module.exports = function isNumber(num) {
var type = typeOf(num);
@@ -63479,10 +63395,10 @@ module.exports = function isNumber(num) {
/***/ }),
-/* 557 */
+/* 555 */
/***/ (function(module, exports, __webpack_require__) {
-var isBuffer = __webpack_require__(558);
+var isBuffer = __webpack_require__(556);
var toString = Object.prototype.toString;
/**
@@ -63601,7 +63517,7 @@ module.exports = function kindOf(val) {
/***/ }),
-/* 558 */
+/* 556 */
/***/ (function(module, exports) {
/*!
@@ -63628,7 +63544,7 @@ function isSlowBuffer (obj) {
/***/ }),
-/* 559 */
+/* 557 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -63705,7 +63621,7 @@ function repeat(str, num) {
/***/ }),
-/* 560 */
+/* 558 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -63718,8 +63634,8 @@ function repeat(str, num) {
-var repeat = __webpack_require__(559);
-var isNumber = __webpack_require__(556);
+var repeat = __webpack_require__(557);
+var isNumber = __webpack_require__(554);
var cache = {};
function toRegexRange(min, max, options) {
@@ -64006,7 +63922,7 @@ module.exports = toRegexRange;
/***/ }),
-/* 561 */
+/* 559 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -64031,14 +63947,14 @@ module.exports = function repeat(ele, num) {
/***/ }),
-/* 562 */
+/* 560 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var Node = __webpack_require__(563);
-var utils = __webpack_require__(550);
+var Node = __webpack_require__(561);
+var utils = __webpack_require__(548);
/**
* Braces parsers
@@ -64398,15 +64314,15 @@ function concatNodes(pos, node, parent, options) {
/***/ }),
-/* 563 */
+/* 561 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var isObject = __webpack_require__(532);
-var define = __webpack_require__(564);
-var utils = __webpack_require__(565);
+var isObject = __webpack_require__(530);
+var define = __webpack_require__(562);
+var utils = __webpack_require__(563);
var ownNames;
/**
@@ -64897,7 +64813,7 @@ exports = module.exports = Node;
/***/ }),
-/* 564 */
+/* 562 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -64910,7 +64826,7 @@ exports = module.exports = Node;
-var isDescriptor = __webpack_require__(533);
+var isDescriptor = __webpack_require__(531);
module.exports = function defineProperty(obj, prop, val) {
if (typeof obj !== 'object' && typeof obj !== 'function') {
@@ -64935,13 +64851,13 @@ module.exports = function defineProperty(obj, prop, val) {
/***/ }),
-/* 565 */
+/* 563 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var typeOf = __webpack_require__(557);
+var typeOf = __webpack_require__(555);
var utils = module.exports;
/**
@@ -65961,17 +65877,17 @@ function assert(val, message) {
/***/ }),
-/* 566 */
+/* 564 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var extend = __webpack_require__(547);
-var Snapdragon = __webpack_require__(567);
-var compilers = __webpack_require__(549);
-var parsers = __webpack_require__(562);
-var utils = __webpack_require__(550);
+var extend = __webpack_require__(545);
+var Snapdragon = __webpack_require__(565);
+var compilers = __webpack_require__(547);
+var parsers = __webpack_require__(560);
+var utils = __webpack_require__(548);
/**
* Customize Snapdragon parser and renderer
@@ -66072,17 +65988,17 @@ module.exports = Braces;
/***/ }),
-/* 567 */
+/* 565 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var Base = __webpack_require__(568);
-var define = __webpack_require__(595);
-var Compiler = __webpack_require__(605);
-var Parser = __webpack_require__(634);
-var utils = __webpack_require__(614);
+var Base = __webpack_require__(566);
+var define = __webpack_require__(593);
+var Compiler = __webpack_require__(603);
+var Parser = __webpack_require__(632);
+var utils = __webpack_require__(612);
var regexCache = {};
var cache = {};
@@ -66253,20 +66169,20 @@ module.exports.Parser = Parser;
/***/ }),
-/* 568 */
+/* 566 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var util = __webpack_require__(112);
-var define = __webpack_require__(569);
-var CacheBase = __webpack_require__(570);
-var Emitter = __webpack_require__(571);
-var isObject = __webpack_require__(532);
-var merge = __webpack_require__(589);
-var pascal = __webpack_require__(592);
-var cu = __webpack_require__(593);
+var define = __webpack_require__(567);
+var CacheBase = __webpack_require__(568);
+var Emitter = __webpack_require__(569);
+var isObject = __webpack_require__(530);
+var merge = __webpack_require__(587);
+var pascal = __webpack_require__(590);
+var cu = __webpack_require__(591);
/**
* Optionally define a custom `cache` namespace to use.
@@ -66695,7 +66611,7 @@ module.exports.namespace = namespace;
/***/ }),
-/* 569 */
+/* 567 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -66708,7 +66624,7 @@ module.exports.namespace = namespace;
-var isDescriptor = __webpack_require__(533);
+var isDescriptor = __webpack_require__(531);
module.exports = function defineProperty(obj, prop, val) {
if (typeof obj !== 'object' && typeof obj !== 'function') {
@@ -66733,21 +66649,21 @@ module.exports = function defineProperty(obj, prop, val) {
/***/ }),
-/* 570 */
+/* 568 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var isObject = __webpack_require__(532);
-var Emitter = __webpack_require__(571);
-var visit = __webpack_require__(572);
-var toPath = __webpack_require__(575);
-var union = __webpack_require__(576);
-var del = __webpack_require__(580);
-var get = __webpack_require__(578);
-var has = __webpack_require__(585);
-var set = __webpack_require__(588);
+var isObject = __webpack_require__(530);
+var Emitter = __webpack_require__(569);
+var visit = __webpack_require__(570);
+var toPath = __webpack_require__(573);
+var union = __webpack_require__(574);
+var del = __webpack_require__(578);
+var get = __webpack_require__(576);
+var has = __webpack_require__(583);
+var set = __webpack_require__(586);
/**
* Create a `Cache` constructor that when instantiated will
@@ -67001,7 +66917,7 @@ module.exports.namespace = namespace;
/***/ }),
-/* 571 */
+/* 569 */
/***/ (function(module, exports, __webpack_require__) {
@@ -67170,7 +67086,7 @@ Emitter.prototype.hasListeners = function(event){
/***/ }),
-/* 572 */
+/* 570 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67183,8 +67099,8 @@ Emitter.prototype.hasListeners = function(event){
-var visit = __webpack_require__(573);
-var mapVisit = __webpack_require__(574);
+var visit = __webpack_require__(571);
+var mapVisit = __webpack_require__(572);
module.exports = function(collection, method, val) {
var result;
@@ -67207,7 +67123,7 @@ module.exports = function(collection, method, val) {
/***/ }),
-/* 573 */
+/* 571 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67220,7 +67136,7 @@ module.exports = function(collection, method, val) {
-var isObject = __webpack_require__(532);
+var isObject = __webpack_require__(530);
module.exports = function visit(thisArg, method, target, val) {
if (!isObject(thisArg) && typeof thisArg !== 'function') {
@@ -67247,14 +67163,14 @@ module.exports = function visit(thisArg, method, target, val) {
/***/ }),
-/* 574 */
+/* 572 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var util = __webpack_require__(112);
-var visit = __webpack_require__(573);
+var visit = __webpack_require__(571);
/**
* Map `visit` over an array of objects.
@@ -67291,7 +67207,7 @@ function isObject(val) {
/***/ }),
-/* 575 */
+/* 573 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67304,7 +67220,7 @@ function isObject(val) {
-var typeOf = __webpack_require__(557);
+var typeOf = __webpack_require__(555);
module.exports = function toPath(args) {
if (typeOf(args) !== 'arguments') {
@@ -67331,16 +67247,16 @@ function filter(arr) {
/***/ }),
-/* 576 */
+/* 574 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var isObject = __webpack_require__(548);
-var union = __webpack_require__(577);
-var get = __webpack_require__(578);
-var set = __webpack_require__(579);
+var isObject = __webpack_require__(546);
+var union = __webpack_require__(575);
+var get = __webpack_require__(576);
+var set = __webpack_require__(577);
module.exports = function unionValue(obj, prop, value) {
if (!isObject(obj)) {
@@ -67368,7 +67284,7 @@ function arrayify(val) {
/***/ }),
-/* 577 */
+/* 575 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67404,7 +67320,7 @@ module.exports = function union(init) {
/***/ }),
-/* 578 */
+/* 576 */
/***/ (function(module, exports) {
/*!
@@ -67460,7 +67376,7 @@ function toString(val) {
/***/ }),
-/* 579 */
+/* 577 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67473,10 +67389,10 @@ function toString(val) {
-var split = __webpack_require__(551);
-var extend = __webpack_require__(547);
-var isPlainObject = __webpack_require__(541);
-var isObject = __webpack_require__(548);
+var split = __webpack_require__(549);
+var extend = __webpack_require__(545);
+var isPlainObject = __webpack_require__(539);
+var isObject = __webpack_require__(546);
module.exports = function(obj, prop, val) {
if (!isObject(obj)) {
@@ -67522,7 +67438,7 @@ function isValidKey(key) {
/***/ }),
-/* 580 */
+/* 578 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67535,8 +67451,8 @@ function isValidKey(key) {
-var isObject = __webpack_require__(532);
-var has = __webpack_require__(581);
+var isObject = __webpack_require__(530);
+var has = __webpack_require__(579);
module.exports = function unset(obj, prop) {
if (!isObject(obj)) {
@@ -67561,7 +67477,7 @@ module.exports = function unset(obj, prop) {
/***/ }),
-/* 581 */
+/* 579 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67574,9 +67490,9 @@ module.exports = function unset(obj, prop) {
-var isObject = __webpack_require__(582);
-var hasValues = __webpack_require__(584);
-var get = __webpack_require__(578);
+var isObject = __webpack_require__(580);
+var hasValues = __webpack_require__(582);
+var get = __webpack_require__(576);
module.exports = function(obj, prop, noZero) {
if (isObject(obj)) {
@@ -67587,7 +67503,7 @@ module.exports = function(obj, prop, noZero) {
/***/ }),
-/* 582 */
+/* 580 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67600,7 +67516,7 @@ module.exports = function(obj, prop, noZero) {
-var isArray = __webpack_require__(583);
+var isArray = __webpack_require__(581);
module.exports = function isObject(val) {
return val != null && typeof val === 'object' && isArray(val) === false;
@@ -67608,7 +67524,7 @@ module.exports = function isObject(val) {
/***/ }),
-/* 583 */
+/* 581 */
/***/ (function(module, exports) {
var toString = {}.toString;
@@ -67619,7 +67535,7 @@ module.exports = Array.isArray || function (arr) {
/***/ }),
-/* 584 */
+/* 582 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67662,7 +67578,7 @@ module.exports = function hasValue(o, noZero) {
/***/ }),
-/* 585 */
+/* 583 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67675,9 +67591,9 @@ module.exports = function hasValue(o, noZero) {
-var isObject = __webpack_require__(532);
-var hasValues = __webpack_require__(586);
-var get = __webpack_require__(578);
+var isObject = __webpack_require__(530);
+var hasValues = __webpack_require__(584);
+var get = __webpack_require__(576);
module.exports = function(val, prop) {
return hasValues(isObject(val) && prop ? get(val, prop) : val);
@@ -67685,7 +67601,7 @@ module.exports = function(val, prop) {
/***/ }),
-/* 586 */
+/* 584 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67698,8 +67614,8 @@ module.exports = function(val, prop) {
-var typeOf = __webpack_require__(587);
-var isNumber = __webpack_require__(556);
+var typeOf = __webpack_require__(585);
+var isNumber = __webpack_require__(554);
module.exports = function hasValue(val) {
// is-number checks for NaN and other edge cases
@@ -67752,10 +67668,10 @@ module.exports = function hasValue(val) {
/***/ }),
-/* 587 */
+/* 585 */
/***/ (function(module, exports, __webpack_require__) {
-var isBuffer = __webpack_require__(558);
+var isBuffer = __webpack_require__(556);
var toString = Object.prototype.toString;
/**
@@ -67877,7 +67793,7 @@ module.exports = function kindOf(val) {
/***/ }),
-/* 588 */
+/* 586 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -67890,10 +67806,10 @@ module.exports = function kindOf(val) {
-var split = __webpack_require__(551);
-var extend = __webpack_require__(547);
-var isPlainObject = __webpack_require__(541);
-var isObject = __webpack_require__(548);
+var split = __webpack_require__(549);
+var extend = __webpack_require__(545);
+var isPlainObject = __webpack_require__(539);
+var isObject = __webpack_require__(546);
module.exports = function(obj, prop, val) {
if (!isObject(obj)) {
@@ -67939,14 +67855,14 @@ function isValidKey(key) {
/***/ }),
-/* 589 */
+/* 587 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var isExtendable = __webpack_require__(590);
-var forIn = __webpack_require__(591);
+var isExtendable = __webpack_require__(588);
+var forIn = __webpack_require__(589);
function mixinDeep(target, objects) {
var len = arguments.length, i = 0;
@@ -68010,7 +67926,7 @@ module.exports = mixinDeep;
/***/ }),
-/* 590 */
+/* 588 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -68023,7 +67939,7 @@ module.exports = mixinDeep;
-var isPlainObject = __webpack_require__(541);
+var isPlainObject = __webpack_require__(539);
module.exports = function isExtendable(val) {
return isPlainObject(val) || typeof val === 'function' || Array.isArray(val);
@@ -68031,7 +67947,7 @@ module.exports = function isExtendable(val) {
/***/ }),
-/* 591 */
+/* 589 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -68054,7 +67970,7 @@ module.exports = function forIn(obj, fn, thisArg) {
/***/ }),
-/* 592 */
+/* 590 */
/***/ (function(module, exports) {
/*!
@@ -68081,14 +67997,14 @@ module.exports = pascalcase;
/***/ }),
-/* 593 */
+/* 591 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var util = __webpack_require__(112);
-var utils = __webpack_require__(594);
+var utils = __webpack_require__(592);
/**
* Expose class utils
@@ -68453,7 +68369,7 @@ cu.bubble = function(Parent, events) {
/***/ }),
-/* 594 */
+/* 592 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -68467,10 +68383,10 @@ var utils = {};
* Lazily required module dependencies
*/
-utils.union = __webpack_require__(577);
-utils.define = __webpack_require__(595);
-utils.isObj = __webpack_require__(532);
-utils.staticExtend = __webpack_require__(602);
+utils.union = __webpack_require__(575);
+utils.define = __webpack_require__(593);
+utils.isObj = __webpack_require__(530);
+utils.staticExtend = __webpack_require__(600);
/**
@@ -68481,7 +68397,7 @@ module.exports = utils;
/***/ }),
-/* 595 */
+/* 593 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -68494,7 +68410,7 @@ module.exports = utils;
-var isDescriptor = __webpack_require__(596);
+var isDescriptor = __webpack_require__(594);
module.exports = function defineProperty(obj, prop, val) {
if (typeof obj !== 'object' && typeof obj !== 'function') {
@@ -68519,7 +68435,7 @@ module.exports = function defineProperty(obj, prop, val) {
/***/ }),
-/* 596 */
+/* 594 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -68532,9 +68448,9 @@ module.exports = function defineProperty(obj, prop, val) {
-var typeOf = __webpack_require__(597);
-var isAccessor = __webpack_require__(598);
-var isData = __webpack_require__(600);
+var typeOf = __webpack_require__(595);
+var isAccessor = __webpack_require__(596);
+var isData = __webpack_require__(598);
module.exports = function isDescriptor(obj, key) {
if (typeOf(obj) !== 'object') {
@@ -68548,7 +68464,7 @@ module.exports = function isDescriptor(obj, key) {
/***/ }),
-/* 597 */
+/* 595 */
/***/ (function(module, exports) {
var toString = Object.prototype.toString;
@@ -68701,7 +68617,7 @@ function isBuffer(val) {
/***/ }),
-/* 598 */
+/* 596 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -68714,7 +68630,7 @@ function isBuffer(val) {
-var typeOf = __webpack_require__(599);
+var typeOf = __webpack_require__(597);
// accessor descriptor properties
var accessor = {
@@ -68777,10 +68693,10 @@ module.exports = isAccessorDescriptor;
/***/ }),
-/* 599 */
+/* 597 */
/***/ (function(module, exports, __webpack_require__) {
-var isBuffer = __webpack_require__(558);
+var isBuffer = __webpack_require__(556);
var toString = Object.prototype.toString;
/**
@@ -68899,7 +68815,7 @@ module.exports = function kindOf(val) {
/***/ }),
-/* 600 */
+/* 598 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -68912,7 +68828,7 @@ module.exports = function kindOf(val) {
-var typeOf = __webpack_require__(601);
+var typeOf = __webpack_require__(599);
// data descriptor properties
var data = {
@@ -68961,10 +68877,10 @@ module.exports = isDataDescriptor;
/***/ }),
-/* 601 */
+/* 599 */
/***/ (function(module, exports, __webpack_require__) {
-var isBuffer = __webpack_require__(558);
+var isBuffer = __webpack_require__(556);
var toString = Object.prototype.toString;
/**
@@ -69083,7 +68999,7 @@ module.exports = function kindOf(val) {
/***/ }),
-/* 602 */
+/* 600 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -69096,8 +69012,8 @@ module.exports = function kindOf(val) {
-var copy = __webpack_require__(603);
-var define = __webpack_require__(595);
+var copy = __webpack_require__(601);
+var define = __webpack_require__(593);
var util = __webpack_require__(112);
/**
@@ -69180,15 +69096,15 @@ module.exports = extend;
/***/ }),
-/* 603 */
+/* 601 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var typeOf = __webpack_require__(557);
-var copyDescriptor = __webpack_require__(604);
-var define = __webpack_require__(595);
+var typeOf = __webpack_require__(555);
+var copyDescriptor = __webpack_require__(602);
+var define = __webpack_require__(593);
/**
* Copy static properties, prototype properties, and descriptors from one object to another.
@@ -69361,7 +69277,7 @@ module.exports.has = has;
/***/ }),
-/* 604 */
+/* 602 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -69449,16 +69365,16 @@ function isObject(val) {
/***/ }),
-/* 605 */
+/* 603 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var use = __webpack_require__(606);
-var define = __webpack_require__(595);
-var debug = __webpack_require__(608)('snapdragon:compiler');
-var utils = __webpack_require__(614);
+var use = __webpack_require__(604);
+var define = __webpack_require__(593);
+var debug = __webpack_require__(606)('snapdragon:compiler');
+var utils = __webpack_require__(612);
/**
* Create a new `Compiler` with the given `options`.
@@ -69612,7 +69528,7 @@ Compiler.prototype = {
// source map support
if (opts.sourcemap) {
- var sourcemaps = __webpack_require__(633);
+ var sourcemaps = __webpack_require__(631);
sourcemaps(this);
this.mapVisit(this.ast.nodes);
this.applySourceMaps();
@@ -69633,7 +69549,7 @@ module.exports = Compiler;
/***/ }),
-/* 606 */
+/* 604 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -69646,7 +69562,7 @@ module.exports = Compiler;
-var utils = __webpack_require__(607);
+var utils = __webpack_require__(605);
module.exports = function base(app, opts) {
if (!utils.isObject(app) && typeof app !== 'function') {
@@ -69761,7 +69677,7 @@ module.exports = function base(app, opts) {
/***/ }),
-/* 607 */
+/* 605 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -69775,8 +69691,8 @@ var utils = {};
* Lazily required module dependencies
*/
-utils.define = __webpack_require__(595);
-utils.isObject = __webpack_require__(532);
+utils.define = __webpack_require__(593);
+utils.isObject = __webpack_require__(530);
utils.isString = function(val) {
@@ -69791,7 +69707,7 @@ module.exports = utils;
/***/ }),
-/* 608 */
+/* 606 */
/***/ (function(module, exports, __webpack_require__) {
/**
@@ -69800,14 +69716,14 @@ module.exports = utils;
*/
if (typeof process !== 'undefined' && process.type === 'renderer') {
- module.exports = __webpack_require__(609);
+ module.exports = __webpack_require__(607);
} else {
- module.exports = __webpack_require__(612);
+ module.exports = __webpack_require__(610);
}
/***/ }),
-/* 609 */
+/* 607 */
/***/ (function(module, exports, __webpack_require__) {
/**
@@ -69816,7 +69732,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') {
* Expose `debug()` as the module.
*/
-exports = module.exports = __webpack_require__(610);
+exports = module.exports = __webpack_require__(608);
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
@@ -69998,7 +69914,7 @@ function localstorage() {
/***/ }),
-/* 610 */
+/* 608 */
/***/ (function(module, exports, __webpack_require__) {
@@ -70014,7 +69930,7 @@ exports.coerce = coerce;
exports.disable = disable;
exports.enable = enable;
exports.enabled = enabled;
-exports.humanize = __webpack_require__(611);
+exports.humanize = __webpack_require__(609);
/**
* The currently active debug mode names, and names to skip.
@@ -70206,7 +70122,7 @@ function coerce(val) {
/***/ }),
-/* 611 */
+/* 609 */
/***/ (function(module, exports) {
/**
@@ -70364,7 +70280,7 @@ function plural(ms, n, name) {
/***/ }),
-/* 612 */
+/* 610 */
/***/ (function(module, exports, __webpack_require__) {
/**
@@ -70380,7 +70296,7 @@ var util = __webpack_require__(112);
* Expose `debug()` as the module.
*/
-exports = module.exports = __webpack_require__(610);
+exports = module.exports = __webpack_require__(608);
exports.init = init;
exports.log = log;
exports.formatArgs = formatArgs;
@@ -70559,7 +70475,7 @@ function createWritableStdioStream (fd) {
case 'PIPE':
case 'TCP':
- var net = __webpack_require__(613);
+ var net = __webpack_require__(611);
stream = new net.Socket({
fd: fd,
readable: false,
@@ -70618,13 +70534,13 @@ exports.enable(load());
/***/ }),
-/* 613 */
+/* 611 */
/***/ (function(module, exports) {
module.exports = require("net");
/***/ }),
-/* 614 */
+/* 612 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -70634,9 +70550,9 @@ module.exports = require("net");
* Module dependencies
*/
-exports.extend = __webpack_require__(547);
-exports.SourceMap = __webpack_require__(615);
-exports.sourceMapResolve = __webpack_require__(626);
+exports.extend = __webpack_require__(545);
+exports.SourceMap = __webpack_require__(613);
+exports.sourceMapResolve = __webpack_require__(624);
/**
* Convert backslash in the given string to forward slashes
@@ -70679,7 +70595,7 @@ exports.last = function(arr, n) {
/***/ }),
-/* 615 */
+/* 613 */
/***/ (function(module, exports, __webpack_require__) {
/*
@@ -70687,13 +70603,13 @@ exports.last = function(arr, n) {
* Licensed under the New BSD license. See LICENSE.txt or:
* http://opensource.org/licenses/BSD-3-Clause
*/
-exports.SourceMapGenerator = __webpack_require__(616).SourceMapGenerator;
-exports.SourceMapConsumer = __webpack_require__(622).SourceMapConsumer;
-exports.SourceNode = __webpack_require__(625).SourceNode;
+exports.SourceMapGenerator = __webpack_require__(614).SourceMapGenerator;
+exports.SourceMapConsumer = __webpack_require__(620).SourceMapConsumer;
+exports.SourceNode = __webpack_require__(623).SourceNode;
/***/ }),
-/* 616 */
+/* 614 */
/***/ (function(module, exports, __webpack_require__) {
/* -*- Mode: js; js-indent-level: 2; -*- */
@@ -70703,10 +70619,10 @@ exports.SourceNode = __webpack_require__(625).SourceNode;
* http://opensource.org/licenses/BSD-3-Clause
*/
-var base64VLQ = __webpack_require__(617);
-var util = __webpack_require__(619);
-var ArraySet = __webpack_require__(620).ArraySet;
-var MappingList = __webpack_require__(621).MappingList;
+var base64VLQ = __webpack_require__(615);
+var util = __webpack_require__(617);
+var ArraySet = __webpack_require__(618).ArraySet;
+var MappingList = __webpack_require__(619).MappingList;
/**
* An instance of the SourceMapGenerator represents a source map which is
@@ -71115,7 +71031,7 @@ exports.SourceMapGenerator = SourceMapGenerator;
/***/ }),
-/* 617 */
+/* 615 */
/***/ (function(module, exports, __webpack_require__) {
/* -*- Mode: js; js-indent-level: 2; -*- */
@@ -71155,7 +71071,7 @@ exports.SourceMapGenerator = SourceMapGenerator;
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-var base64 = __webpack_require__(618);
+var base64 = __webpack_require__(616);
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
// length quantities we use in the source map spec, the first bit is the sign,
@@ -71261,7 +71177,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
/***/ }),
-/* 618 */
+/* 616 */
/***/ (function(module, exports) {
/* -*- Mode: js; js-indent-level: 2; -*- */
@@ -71334,7 +71250,7 @@ exports.decode = function (charCode) {
/***/ }),
-/* 619 */
+/* 617 */
/***/ (function(module, exports) {
/* -*- Mode: js; js-indent-level: 2; -*- */
@@ -71757,7 +71673,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate
/***/ }),
-/* 620 */
+/* 618 */
/***/ (function(module, exports, __webpack_require__) {
/* -*- Mode: js; js-indent-level: 2; -*- */
@@ -71767,7 +71683,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate
* http://opensource.org/licenses/BSD-3-Clause
*/
-var util = __webpack_require__(619);
+var util = __webpack_require__(617);
var has = Object.prototype.hasOwnProperty;
var hasNativeMap = typeof Map !== "undefined";
@@ -71884,7 +71800,7 @@ exports.ArraySet = ArraySet;
/***/ }),
-/* 621 */
+/* 619 */
/***/ (function(module, exports, __webpack_require__) {
/* -*- Mode: js; js-indent-level: 2; -*- */
@@ -71894,7 +71810,7 @@ exports.ArraySet = ArraySet;
* http://opensource.org/licenses/BSD-3-Clause
*/
-var util = __webpack_require__(619);
+var util = __webpack_require__(617);
/**
* Determine whether mappingB is after mappingA with respect to generated
@@ -71969,7 +71885,7 @@ exports.MappingList = MappingList;
/***/ }),
-/* 622 */
+/* 620 */
/***/ (function(module, exports, __webpack_require__) {
/* -*- Mode: js; js-indent-level: 2; -*- */
@@ -71979,11 +71895,11 @@ exports.MappingList = MappingList;
* http://opensource.org/licenses/BSD-3-Clause
*/
-var util = __webpack_require__(619);
-var binarySearch = __webpack_require__(623);
-var ArraySet = __webpack_require__(620).ArraySet;
-var base64VLQ = __webpack_require__(617);
-var quickSort = __webpack_require__(624).quickSort;
+var util = __webpack_require__(617);
+var binarySearch = __webpack_require__(621);
+var ArraySet = __webpack_require__(618).ArraySet;
+var base64VLQ = __webpack_require__(615);
+var quickSort = __webpack_require__(622).quickSort;
function SourceMapConsumer(aSourceMap) {
var sourceMap = aSourceMap;
@@ -73057,7 +72973,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;
/***/ }),
-/* 623 */
+/* 621 */
/***/ (function(module, exports) {
/* -*- Mode: js; js-indent-level: 2; -*- */
@@ -73174,7 +73090,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
/***/ }),
-/* 624 */
+/* 622 */
/***/ (function(module, exports) {
/* -*- Mode: js; js-indent-level: 2; -*- */
@@ -73294,7 +73210,7 @@ exports.quickSort = function (ary, comparator) {
/***/ }),
-/* 625 */
+/* 623 */
/***/ (function(module, exports, __webpack_require__) {
/* -*- Mode: js; js-indent-level: 2; -*- */
@@ -73304,8 +73220,8 @@ exports.quickSort = function (ary, comparator) {
* http://opensource.org/licenses/BSD-3-Clause
*/
-var SourceMapGenerator = __webpack_require__(616).SourceMapGenerator;
-var util = __webpack_require__(619);
+var SourceMapGenerator = __webpack_require__(614).SourceMapGenerator;
+var util = __webpack_require__(617);
// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
// operating systems these days (capturing the result).
@@ -73713,17 +73629,17 @@ exports.SourceNode = SourceNode;
/***/ }),
-/* 626 */
+/* 624 */
/***/ (function(module, exports, __webpack_require__) {
// Copyright 2014, 2015, 2016, 2017 Simon Lydell
// X11 (“MIT”) Licensed. (See LICENSE.)
-var sourceMappingURL = __webpack_require__(627)
-var resolveUrl = __webpack_require__(628)
-var decodeUriComponent = __webpack_require__(629)
-var urix = __webpack_require__(631)
-var atob = __webpack_require__(632)
+var sourceMappingURL = __webpack_require__(625)
+var resolveUrl = __webpack_require__(626)
+var decodeUriComponent = __webpack_require__(627)
+var urix = __webpack_require__(629)
+var atob = __webpack_require__(630)
@@ -74021,7 +73937,7 @@ module.exports = {
/***/ }),
-/* 627 */
+/* 625 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell
@@ -74084,7 +74000,7 @@ void (function(root, factory) {
/***/ }),
-/* 628 */
+/* 626 */
/***/ (function(module, exports, __webpack_require__) {
// Copyright 2014 Simon Lydell
@@ -74102,13 +74018,13 @@ module.exports = resolveUrl
/***/ }),
-/* 629 */
+/* 627 */
/***/ (function(module, exports, __webpack_require__) {
// Copyright 2017 Simon Lydell
// X11 (“MIT”) Licensed. (See LICENSE.)
-var decodeUriComponent = __webpack_require__(630)
+var decodeUriComponent = __webpack_require__(628)
function customDecodeUriComponent(string) {
// `decodeUriComponent` turns `+` into ` `, but that's not wanted.
@@ -74119,7 +74035,7 @@ module.exports = customDecodeUriComponent
/***/ }),
-/* 630 */
+/* 628 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -74220,7 +74136,7 @@ module.exports = function (encodedURI) {
/***/ }),
-/* 631 */
+/* 629 */
/***/ (function(module, exports, __webpack_require__) {
// Copyright 2014 Simon Lydell
@@ -74243,7 +74159,7 @@ module.exports = urix
/***/ }),
-/* 632 */
+/* 630 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -74257,7 +74173,7 @@ module.exports = atob.atob = atob;
/***/ }),
-/* 633 */
+/* 631 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -74265,8 +74181,8 @@ module.exports = atob.atob = atob;
var fs = __webpack_require__(134);
var path = __webpack_require__(4);
-var define = __webpack_require__(595);
-var utils = __webpack_require__(614);
+var define = __webpack_require__(593);
+var utils = __webpack_require__(612);
/**
* Expose `mixin()`.
@@ -74409,19 +74325,19 @@ exports.comment = function(node) {
/***/ }),
-/* 634 */
+/* 632 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var use = __webpack_require__(606);
+var use = __webpack_require__(604);
var util = __webpack_require__(112);
-var Cache = __webpack_require__(635);
-var define = __webpack_require__(595);
-var debug = __webpack_require__(608)('snapdragon:parser');
-var Position = __webpack_require__(636);
-var utils = __webpack_require__(614);
+var Cache = __webpack_require__(633);
+var define = __webpack_require__(593);
+var debug = __webpack_require__(606)('snapdragon:parser');
+var Position = __webpack_require__(634);
+var utils = __webpack_require__(612);
/**
* Create a new `Parser` with the given `input` and `options`.
@@ -74949,7 +74865,7 @@ module.exports = Parser;
/***/ }),
-/* 635 */
+/* 633 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -75056,13 +74972,13 @@ MapCache.prototype.del = function mapDelete(key) {
/***/ }),
-/* 636 */
+/* 634 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var define = __webpack_require__(595);
+var define = __webpack_require__(593);
/**
* Store position for a node
@@ -75077,14 +74993,14 @@ module.exports = function Position(start, parser) {
/***/ }),
-/* 637 */
+/* 635 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var isExtendable = __webpack_require__(638);
-var assignSymbols = __webpack_require__(542);
+var isExtendable = __webpack_require__(636);
+var assignSymbols = __webpack_require__(540);
module.exports = Object.assign || function(obj/*, objects*/) {
if (obj === null || typeof obj === 'undefined') {
@@ -75144,7 +75060,7 @@ function isEnum(obj, key) {
/***/ }),
-/* 638 */
+/* 636 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -75157,7 +75073,7 @@ function isEnum(obj, key) {
-var isPlainObject = __webpack_require__(541);
+var isPlainObject = __webpack_require__(539);
module.exports = function isExtendable(val) {
return isPlainObject(val) || typeof val === 'function' || Array.isArray(val);
@@ -75165,14 +75081,14 @@ module.exports = function isExtendable(val) {
/***/ }),
-/* 639 */
+/* 637 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var nanomatch = __webpack_require__(640);
-var extglob = __webpack_require__(655);
+var nanomatch = __webpack_require__(638);
+var extglob = __webpack_require__(653);
module.exports = function(snapdragon) {
var compilers = snapdragon.compiler.compilers;
@@ -75249,7 +75165,7 @@ function escapeExtglobs(compiler) {
/***/ }),
-/* 640 */
+/* 638 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -75260,17 +75176,17 @@ function escapeExtglobs(compiler) {
*/
var util = __webpack_require__(112);
-var toRegex = __webpack_require__(524);
-var extend = __webpack_require__(641);
+var toRegex = __webpack_require__(522);
+var extend = __webpack_require__(639);
/**
* Local dependencies
*/
-var compilers = __webpack_require__(643);
-var parsers = __webpack_require__(644);
-var cache = __webpack_require__(647);
-var utils = __webpack_require__(649);
+var compilers = __webpack_require__(641);
+var parsers = __webpack_require__(642);
+var cache = __webpack_require__(645);
+var utils = __webpack_require__(647);
var MAX_LENGTH = 1024 * 64;
/**
@@ -76094,14 +76010,14 @@ module.exports = nanomatch;
/***/ }),
-/* 641 */
+/* 639 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var isExtendable = __webpack_require__(642);
-var assignSymbols = __webpack_require__(542);
+var isExtendable = __webpack_require__(640);
+var assignSymbols = __webpack_require__(540);
module.exports = Object.assign || function(obj/*, objects*/) {
if (obj === null || typeof obj === 'undefined') {
@@ -76161,7 +76077,7 @@ function isEnum(obj, key) {
/***/ }),
-/* 642 */
+/* 640 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -76174,7 +76090,7 @@ function isEnum(obj, key) {
-var isPlainObject = __webpack_require__(541);
+var isPlainObject = __webpack_require__(539);
module.exports = function isExtendable(val) {
return isPlainObject(val) || typeof val === 'function' || Array.isArray(val);
@@ -76182,7 +76098,7 @@ module.exports = function isExtendable(val) {
/***/ }),
-/* 643 */
+/* 641 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -76528,15 +76444,15 @@ module.exports = function(nanomatch, options) {
/***/ }),
-/* 644 */
+/* 642 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var regexNot = __webpack_require__(543);
-var toRegex = __webpack_require__(524);
-var isOdd = __webpack_require__(645);
+var regexNot = __webpack_require__(541);
+var toRegex = __webpack_require__(522);
+var isOdd = __webpack_require__(643);
/**
* Characters to use in negation regex (we want to "not" match
@@ -76922,7 +76838,7 @@ module.exports.not = NOT_REGEX;
/***/ }),
-/* 645 */
+/* 643 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -76935,7 +76851,7 @@ module.exports.not = NOT_REGEX;
-var isNumber = __webpack_require__(646);
+var isNumber = __webpack_require__(644);
module.exports = function isOdd(i) {
if (!isNumber(i)) {
@@ -76949,7 +76865,7 @@ module.exports = function isOdd(i) {
/***/ }),
-/* 646 */
+/* 644 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -76977,14 +76893,14 @@ module.exports = function isNumber(num) {
/***/ }),
-/* 647 */
+/* 645 */
/***/ (function(module, exports, __webpack_require__) {
-module.exports = new (__webpack_require__(648))();
+module.exports = new (__webpack_require__(646))();
/***/ }),
-/* 648 */
+/* 646 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -76997,7 +76913,7 @@ module.exports = new (__webpack_require__(648))();
-var MapCache = __webpack_require__(635);
+var MapCache = __webpack_require__(633);
/**
* Create a new `FragmentCache` with an optional object to use for `caches`.
@@ -77119,7 +77035,7 @@ exports = module.exports = FragmentCache;
/***/ }),
-/* 649 */
+/* 647 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -77132,14 +77048,14 @@ var path = __webpack_require__(4);
* Module dependencies
*/
-var isWindows = __webpack_require__(650)();
-var Snapdragon = __webpack_require__(567);
-utils.define = __webpack_require__(651);
-utils.diff = __webpack_require__(652);
-utils.extend = __webpack_require__(641);
-utils.pick = __webpack_require__(653);
-utils.typeOf = __webpack_require__(654);
-utils.unique = __webpack_require__(546);
+var isWindows = __webpack_require__(648)();
+var Snapdragon = __webpack_require__(565);
+utils.define = __webpack_require__(649);
+utils.diff = __webpack_require__(650);
+utils.extend = __webpack_require__(639);
+utils.pick = __webpack_require__(651);
+utils.typeOf = __webpack_require__(652);
+utils.unique = __webpack_require__(544);
/**
* Returns true if the given value is effectively an empty string
@@ -77505,7 +77421,7 @@ utils.unixify = function(options) {
/***/ }),
-/* 650 */
+/* 648 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
@@ -77533,7 +77449,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
/***/ }),
-/* 651 */
+/* 649 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -77546,8 +77462,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
-var isobject = __webpack_require__(532);
-var isDescriptor = __webpack_require__(533);
+var isobject = __webpack_require__(530);
+var isDescriptor = __webpack_require__(531);
var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty)
? Reflect.defineProperty
: Object.defineProperty;
@@ -77578,7 +77494,7 @@ module.exports = function defineProperty(obj, key, val) {
/***/ }),
-/* 652 */
+/* 650 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -77632,7 +77548,7 @@ function diffArray(one, two) {
/***/ }),
-/* 653 */
+/* 651 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -77645,7 +77561,7 @@ function diffArray(one, two) {
-var isObject = __webpack_require__(532);
+var isObject = __webpack_require__(530);
module.exports = function pick(obj, keys) {
if (!isObject(obj) && typeof obj !== 'function') {
@@ -77674,7 +77590,7 @@ module.exports = function pick(obj, keys) {
/***/ }),
-/* 654 */
+/* 652 */
/***/ (function(module, exports) {
var toString = Object.prototype.toString;
@@ -77809,7 +77725,7 @@ function isBuffer(val) {
/***/ }),
-/* 655 */
+/* 653 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -77819,18 +77735,18 @@ function isBuffer(val) {
* Module dependencies
*/
-var extend = __webpack_require__(547);
-var unique = __webpack_require__(546);
-var toRegex = __webpack_require__(524);
+var extend = __webpack_require__(545);
+var unique = __webpack_require__(544);
+var toRegex = __webpack_require__(522);
/**
* Local dependencies
*/
-var compilers = __webpack_require__(656);
-var parsers = __webpack_require__(662);
-var Extglob = __webpack_require__(665);
-var utils = __webpack_require__(664);
+var compilers = __webpack_require__(654);
+var parsers = __webpack_require__(660);
+var Extglob = __webpack_require__(663);
+var utils = __webpack_require__(662);
var MAX_LENGTH = 1024 * 64;
/**
@@ -78147,13 +78063,13 @@ module.exports = extglob;
/***/ }),
-/* 656 */
+/* 654 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var brackets = __webpack_require__(657);
+var brackets = __webpack_require__(655);
/**
* Extglob compilers
@@ -78323,7 +78239,7 @@ module.exports = function(extglob) {
/***/ }),
-/* 657 */
+/* 655 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -78333,17 +78249,17 @@ module.exports = function(extglob) {
* Local dependencies
*/
-var compilers = __webpack_require__(658);
-var parsers = __webpack_require__(660);
+var compilers = __webpack_require__(656);
+var parsers = __webpack_require__(658);
/**
* Module dependencies
*/
-var debug = __webpack_require__(608)('expand-brackets');
-var extend = __webpack_require__(547);
-var Snapdragon = __webpack_require__(567);
-var toRegex = __webpack_require__(524);
+var debug = __webpack_require__(606)('expand-brackets');
+var extend = __webpack_require__(545);
+var Snapdragon = __webpack_require__(565);
+var toRegex = __webpack_require__(522);
/**
* Parses the given POSIX character class `pattern` and returns a
@@ -78541,13 +78457,13 @@ module.exports = brackets;
/***/ }),
-/* 658 */
+/* 656 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var posix = __webpack_require__(659);
+var posix = __webpack_require__(657);
module.exports = function(brackets) {
brackets.compiler
@@ -78635,7 +78551,7 @@ module.exports = function(brackets) {
/***/ }),
-/* 659 */
+/* 657 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -78664,14 +78580,14 @@ module.exports = {
/***/ }),
-/* 660 */
+/* 658 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var utils = __webpack_require__(661);
-var define = __webpack_require__(595);
+var utils = __webpack_require__(659);
+var define = __webpack_require__(593);
/**
* Text regex
@@ -78890,14 +78806,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX;
/***/ }),
-/* 661 */
+/* 659 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var toRegex = __webpack_require__(524);
-var regexNot = __webpack_require__(543);
+var toRegex = __webpack_require__(522);
+var regexNot = __webpack_require__(541);
var cached;
/**
@@ -78931,15 +78847,15 @@ exports.createRegex = function(pattern, include) {
/***/ }),
-/* 662 */
+/* 660 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var brackets = __webpack_require__(657);
-var define = __webpack_require__(663);
-var utils = __webpack_require__(664);
+var brackets = __webpack_require__(655);
+var define = __webpack_require__(661);
+var utils = __webpack_require__(662);
/**
* Characters to use in text regex (we want to "not" match
@@ -79094,7 +79010,7 @@ module.exports = parsers;
/***/ }),
-/* 663 */
+/* 661 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -79107,7 +79023,7 @@ module.exports = parsers;
-var isDescriptor = __webpack_require__(533);
+var isDescriptor = __webpack_require__(531);
module.exports = function defineProperty(obj, prop, val) {
if (typeof obj !== 'object' && typeof obj !== 'function') {
@@ -79132,14 +79048,14 @@ module.exports = function defineProperty(obj, prop, val) {
/***/ }),
-/* 664 */
+/* 662 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var regex = __webpack_require__(543);
-var Cache = __webpack_require__(648);
+var regex = __webpack_require__(541);
+var Cache = __webpack_require__(646);
/**
* Utils
@@ -79208,7 +79124,7 @@ utils.createRegex = function(str) {
/***/ }),
-/* 665 */
+/* 663 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -79218,16 +79134,16 @@ utils.createRegex = function(str) {
* Module dependencies
*/
-var Snapdragon = __webpack_require__(567);
-var define = __webpack_require__(663);
-var extend = __webpack_require__(547);
+var Snapdragon = __webpack_require__(565);
+var define = __webpack_require__(661);
+var extend = __webpack_require__(545);
/**
* Local dependencies
*/
-var compilers = __webpack_require__(656);
-var parsers = __webpack_require__(662);
+var compilers = __webpack_require__(654);
+var parsers = __webpack_require__(660);
/**
* Customize Snapdragon parser and renderer
@@ -79293,16 +79209,16 @@ module.exports = Extglob;
/***/ }),
-/* 666 */
+/* 664 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-var extglob = __webpack_require__(655);
-var nanomatch = __webpack_require__(640);
-var regexNot = __webpack_require__(543);
-var toRegex = __webpack_require__(524);
+var extglob = __webpack_require__(653);
+var nanomatch = __webpack_require__(638);
+var regexNot = __webpack_require__(541);
+var toRegex = __webpack_require__(522);
var not;
/**
@@ -79383,14 +79299,14 @@ function textRegex(pattern) {
/***/ }),
-/* 667 */
+/* 665 */
/***/ (function(module, exports, __webpack_require__) {
-module.exports = new (__webpack_require__(648))();
+module.exports = new (__webpack_require__(646))();
/***/ }),
-/* 668 */
+/* 666 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -79403,13 +79319,13 @@ var path = __webpack_require__(4);
* Module dependencies
*/
-var Snapdragon = __webpack_require__(567);
-utils.define = __webpack_require__(669);
-utils.diff = __webpack_require__(652);
-utils.extend = __webpack_require__(637);
-utils.pick = __webpack_require__(653);
-utils.typeOf = __webpack_require__(670);
-utils.unique = __webpack_require__(546);
+var Snapdragon = __webpack_require__(565);
+utils.define = __webpack_require__(667);
+utils.diff = __webpack_require__(650);
+utils.extend = __webpack_require__(635);
+utils.pick = __webpack_require__(651);
+utils.typeOf = __webpack_require__(668);
+utils.unique = __webpack_require__(544);
/**
* Returns true if the platform is windows, or `path.sep` is `\\`.
@@ -79706,7 +79622,7 @@ utils.unixify = function(options) {
/***/ }),
-/* 669 */
+/* 667 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -79719,8 +79635,8 @@ utils.unixify = function(options) {
-var isobject = __webpack_require__(532);
-var isDescriptor = __webpack_require__(533);
+var isobject = __webpack_require__(530);
+var isDescriptor = __webpack_require__(531);
var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty)
? Reflect.defineProperty
: Object.defineProperty;
@@ -79751,7 +79667,7 @@ module.exports = function defineProperty(obj, key, val) {
/***/ }),
-/* 670 */
+/* 668 */
/***/ (function(module, exports) {
var toString = Object.prototype.toString;
@@ -79886,7 +79802,7 @@ function isBuffer(val) {
/***/ }),
-/* 671 */
+/* 669 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -79905,9 +79821,9 @@ var __extends = (this && this.__extends) || (function () {
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
-var readdir = __webpack_require__(672);
-var reader_1 = __webpack_require__(685);
-var fs_stream_1 = __webpack_require__(689);
+var readdir = __webpack_require__(670);
+var reader_1 = __webpack_require__(683);
+var fs_stream_1 = __webpack_require__(687);
var ReaderAsync = /** @class */ (function (_super) {
__extends(ReaderAsync, _super);
function ReaderAsync() {
@@ -79968,15 +79884,15 @@ exports.default = ReaderAsync;
/***/ }),
-/* 672 */
+/* 670 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const readdirSync = __webpack_require__(673);
-const readdirAsync = __webpack_require__(681);
-const readdirStream = __webpack_require__(684);
+const readdirSync = __webpack_require__(671);
+const readdirAsync = __webpack_require__(679);
+const readdirStream = __webpack_require__(682);
module.exports = exports = readdirAsyncPath;
exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath;
@@ -80060,7 +79976,7 @@ function readdirStreamStat (dir, options) {
/***/ }),
-/* 673 */
+/* 671 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -80068,11 +79984,11 @@ function readdirStreamStat (dir, options) {
module.exports = readdirSync;
-const DirectoryReader = __webpack_require__(674);
+const DirectoryReader = __webpack_require__(672);
let syncFacade = {
- fs: __webpack_require__(679),
- forEach: __webpack_require__(680),
+ fs: __webpack_require__(677),
+ forEach: __webpack_require__(678),
sync: true
};
@@ -80101,7 +80017,7 @@ function readdirSync (dir, options, internalOptions) {
/***/ }),
-/* 674 */
+/* 672 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -80110,9 +80026,9 @@ function readdirSync (dir, options, internalOptions) {
const Readable = __webpack_require__(138).Readable;
const EventEmitter = __webpack_require__(156).EventEmitter;
const path = __webpack_require__(4);
-const normalizeOptions = __webpack_require__(675);
-const stat = __webpack_require__(677);
-const call = __webpack_require__(678);
+const normalizeOptions = __webpack_require__(673);
+const stat = __webpack_require__(675);
+const call = __webpack_require__(676);
/**
* Asynchronously reads the contents of a directory and streams the results
@@ -80488,14 +80404,14 @@ module.exports = DirectoryReader;
/***/ }),
-/* 675 */
+/* 673 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const path = __webpack_require__(4);
-const globToRegExp = __webpack_require__(676);
+const globToRegExp = __webpack_require__(674);
module.exports = normalizeOptions;
@@ -80672,7 +80588,7 @@ function normalizeOptions (options, internalOptions) {
/***/ }),
-/* 676 */
+/* 674 */
/***/ (function(module, exports) {
module.exports = function (glob, opts) {
@@ -80809,13 +80725,13 @@ module.exports = function (glob, opts) {
/***/ }),
-/* 677 */
+/* 675 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const call = __webpack_require__(678);
+const call = __webpack_require__(676);
module.exports = stat;
@@ -80890,7 +80806,7 @@ function symlinkStat (fs, path, lstats, callback) {
/***/ }),
-/* 678 */
+/* 676 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -80951,14 +80867,14 @@ function callOnce (fn) {
/***/ }),
-/* 679 */
+/* 677 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const fs = __webpack_require__(134);
-const call = __webpack_require__(678);
+const call = __webpack_require__(676);
/**
* A facade around {@link fs.readdirSync} that allows it to be called
@@ -81022,7 +80938,7 @@ exports.lstat = function (path, callback) {
/***/ }),
-/* 680 */
+/* 678 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81051,7 +80967,7 @@ function syncForEach (array, iterator, done) {
/***/ }),
-/* 681 */
+/* 679 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81059,12 +80975,12 @@ function syncForEach (array, iterator, done) {
module.exports = readdirAsync;
-const maybe = __webpack_require__(682);
-const DirectoryReader = __webpack_require__(674);
+const maybe = __webpack_require__(680);
+const DirectoryReader = __webpack_require__(672);
let asyncFacade = {
fs: __webpack_require__(134),
- forEach: __webpack_require__(683),
+ forEach: __webpack_require__(681),
async: true
};
@@ -81106,7 +81022,7 @@ function readdirAsync (dir, options, callback, internalOptions) {
/***/ }),
-/* 682 */
+/* 680 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81133,7 +81049,7 @@ module.exports = function maybe (cb, promise) {
/***/ }),
-/* 683 */
+/* 681 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81169,7 +81085,7 @@ function asyncForEach (array, iterator, done) {
/***/ }),
-/* 684 */
+/* 682 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81177,11 +81093,11 @@ function asyncForEach (array, iterator, done) {
module.exports = readdirStream;
-const DirectoryReader = __webpack_require__(674);
+const DirectoryReader = __webpack_require__(672);
let streamFacade = {
fs: __webpack_require__(134),
- forEach: __webpack_require__(683),
+ forEach: __webpack_require__(681),
async: true
};
@@ -81201,16 +81117,16 @@ function readdirStream (dir, options, internalOptions) {
/***/ }),
-/* 685 */
+/* 683 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var path = __webpack_require__(4);
-var deep_1 = __webpack_require__(686);
-var entry_1 = __webpack_require__(688);
-var pathUtil = __webpack_require__(687);
+var deep_1 = __webpack_require__(684);
+var entry_1 = __webpack_require__(686);
+var pathUtil = __webpack_require__(685);
var Reader = /** @class */ (function () {
function Reader(options) {
this.options = options;
@@ -81276,14 +81192,14 @@ exports.default = Reader;
/***/ }),
-/* 686 */
+/* 684 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-var pathUtils = __webpack_require__(687);
-var patternUtils = __webpack_require__(518);
+var pathUtils = __webpack_require__(685);
+var patternUtils = __webpack_require__(516);
var DeepFilter = /** @class */ (function () {
function DeepFilter(options, micromatchOptions) {
this.options = options;
@@ -81366,7 +81282,7 @@ exports.default = DeepFilter;
/***/ }),
-/* 687 */
+/* 685 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81397,14 +81313,14 @@ exports.makeAbsolute = makeAbsolute;
/***/ }),
-/* 688 */
+/* 686 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-var pathUtils = __webpack_require__(687);
-var patternUtils = __webpack_require__(518);
+var pathUtils = __webpack_require__(685);
+var patternUtils = __webpack_require__(516);
var EntryFilter = /** @class */ (function () {
function EntryFilter(options, micromatchOptions) {
this.options = options;
@@ -81489,7 +81405,7 @@ exports.default = EntryFilter;
/***/ }),
-/* 689 */
+/* 687 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81509,8 +81425,8 @@ var __extends = (this && this.__extends) || (function () {
})();
Object.defineProperty(exports, "__esModule", { value: true });
var stream = __webpack_require__(138);
-var fsStat = __webpack_require__(690);
-var fs_1 = __webpack_require__(694);
+var fsStat = __webpack_require__(688);
+var fs_1 = __webpack_require__(692);
var FileSystemStream = /** @class */ (function (_super) {
__extends(FileSystemStream, _super);
function FileSystemStream() {
@@ -81560,14 +81476,14 @@ exports.default = FileSystemStream;
/***/ }),
-/* 690 */
+/* 688 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-const optionsManager = __webpack_require__(691);
-const statProvider = __webpack_require__(693);
+const optionsManager = __webpack_require__(689);
+const statProvider = __webpack_require__(691);
/**
* Asynchronous API.
*/
@@ -81598,13 +81514,13 @@ exports.statSync = statSync;
/***/ }),
-/* 691 */
+/* 689 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-const fsAdapter = __webpack_require__(692);
+const fsAdapter = __webpack_require__(690);
function prepare(opts) {
const options = Object.assign({
fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined),
@@ -81617,7 +81533,7 @@ exports.prepare = prepare;
/***/ }),
-/* 692 */
+/* 690 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81640,7 +81556,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter;
/***/ }),
-/* 693 */
+/* 691 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81692,7 +81608,7 @@ exports.isFollowedSymlink = isFollowedSymlink;
/***/ }),
-/* 694 */
+/* 692 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81723,7 +81639,7 @@ exports.default = FileSystem;
/***/ }),
-/* 695 */
+/* 693 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81743,9 +81659,9 @@ var __extends = (this && this.__extends) || (function () {
})();
Object.defineProperty(exports, "__esModule", { value: true });
var stream = __webpack_require__(138);
-var readdir = __webpack_require__(672);
-var reader_1 = __webpack_require__(685);
-var fs_stream_1 = __webpack_require__(689);
+var readdir = __webpack_require__(670);
+var reader_1 = __webpack_require__(683);
+var fs_stream_1 = __webpack_require__(687);
var TransformStream = /** @class */ (function (_super) {
__extends(TransformStream, _super);
function TransformStream(reader) {
@@ -81813,7 +81729,7 @@ exports.default = ReaderStream;
/***/ }),
-/* 696 */
+/* 694 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81832,9 +81748,9 @@ var __extends = (this && this.__extends) || (function () {
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
-var readdir = __webpack_require__(672);
-var reader_1 = __webpack_require__(685);
-var fs_sync_1 = __webpack_require__(697);
+var readdir = __webpack_require__(670);
+var reader_1 = __webpack_require__(683);
+var fs_sync_1 = __webpack_require__(695);
var ReaderSync = /** @class */ (function (_super) {
__extends(ReaderSync, _super);
function ReaderSync() {
@@ -81894,7 +81810,7 @@ exports.default = ReaderSync;
/***/ }),
-/* 697 */
+/* 695 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81913,8 +81829,8 @@ var __extends = (this && this.__extends) || (function () {
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
-var fsStat = __webpack_require__(690);
-var fs_1 = __webpack_require__(694);
+var fsStat = __webpack_require__(688);
+var fs_1 = __webpack_require__(692);
var FileSystemSync = /** @class */ (function (_super) {
__extends(FileSystemSync, _super);
function FileSystemSync() {
@@ -81960,7 +81876,7 @@ exports.default = FileSystemSync;
/***/ }),
-/* 698 */
+/* 696 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81976,7 +81892,7 @@ exports.flatten = flatten;
/***/ }),
-/* 699 */
+/* 697 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -81997,13 +81913,13 @@ exports.merge = merge;
/***/ }),
-/* 700 */
+/* 698 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const path = __webpack_require__(4);
-const pathType = __webpack_require__(701);
+const pathType = __webpack_require__(699);
const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0];
@@ -82069,13 +81985,13 @@ module.exports.sync = (input, opts) => {
/***/ }),
-/* 701 */
+/* 699 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const fs = __webpack_require__(134);
-const pify = __webpack_require__(702);
+const pify = __webpack_require__(700);
function type(fn, fn2, fp) {
if (typeof fp !== 'string') {
@@ -82118,7 +82034,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink');
/***/ }),
-/* 702 */
+/* 700 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -82209,17 +82125,17 @@ module.exports = (obj, opts) => {
/***/ }),
-/* 703 */
+/* 701 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const fs = __webpack_require__(134);
const path = __webpack_require__(4);
-const fastGlob = __webpack_require__(514);
-const gitIgnore = __webpack_require__(704);
-const pify = __webpack_require__(705);
-const slash = __webpack_require__(706);
+const fastGlob = __webpack_require__(512);
+const gitIgnore = __webpack_require__(702);
+const pify = __webpack_require__(703);
+const slash = __webpack_require__(704);
const DEFAULT_IGNORE = [
'**/node_modules/**',
@@ -82317,7 +82233,7 @@ module.exports.sync = options => {
/***/ }),
-/* 704 */
+/* 702 */
/***/ (function(module, exports) {
// A simple implementation of make-array
@@ -82786,7 +82702,7 @@ module.exports = options => new IgnoreBase(options)
/***/ }),
-/* 705 */
+/* 703 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -82861,7 +82777,7 @@ module.exports = (input, options) => {
/***/ }),
-/* 706 */
+/* 704 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -82879,7 +82795,7 @@ module.exports = input => {
/***/ }),
-/* 707 */
+/* 705 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -82892,7 +82808,7 @@ module.exports = input => {
-var isGlob = __webpack_require__(708);
+var isGlob = __webpack_require__(706);
module.exports = function hasGlob(val) {
if (val == null) return false;
@@ -82912,7 +82828,7 @@ module.exports = function hasGlob(val) {
/***/ }),
-/* 708 */
+/* 706 */
/***/ (function(module, exports, __webpack_require__) {
/*!
@@ -82943,17 +82859,17 @@ module.exports = function isGlob(str) {
/***/ }),
-/* 709 */
+/* 707 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const path = __webpack_require__(4);
const {constants: fsConstants} = __webpack_require__(134);
-const pEvent = __webpack_require__(710);
-const CpFileError = __webpack_require__(713);
-const fs = __webpack_require__(715);
-const ProgressEmitter = __webpack_require__(718);
+const pEvent = __webpack_require__(708);
+const CpFileError = __webpack_require__(711);
+const fs = __webpack_require__(713);
+const ProgressEmitter = __webpack_require__(716);
const cpFileAsync = async (source, destination, options, progressEmitter) => {
let readError;
@@ -83067,12 +82983,12 @@ module.exports.sync = (source, destination, options) => {
/***/ }),
-/* 710 */
+/* 708 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const pTimeout = __webpack_require__(711);
+const pTimeout = __webpack_require__(709);
const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator';
@@ -83363,12 +83279,12 @@ module.exports.iterator = (emitter, event, options) => {
/***/ }),
-/* 711 */
+/* 709 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const pFinally = __webpack_require__(712);
+const pFinally = __webpack_require__(710);
class TimeoutError extends Error {
constructor(message) {
@@ -83414,7 +83330,7 @@ module.exports.TimeoutError = TimeoutError;
/***/ }),
-/* 712 */
+/* 710 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -83436,12 +83352,12 @@ module.exports = (promise, onFinally) => {
/***/ }),
-/* 713 */
+/* 711 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const NestedError = __webpack_require__(714);
+const NestedError = __webpack_require__(712);
class CpFileError extends NestedError {
constructor(message, nested) {
@@ -83455,7 +83371,7 @@ module.exports = CpFileError;
/***/ }),
-/* 714 */
+/* 712 */
/***/ (function(module, exports, __webpack_require__) {
var inherits = __webpack_require__(112).inherits;
@@ -83511,16 +83427,16 @@ module.exports = NestedError;
/***/ }),
-/* 715 */
+/* 713 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const {promisify} = __webpack_require__(112);
const fs = __webpack_require__(133);
-const makeDir = __webpack_require__(716);
-const pEvent = __webpack_require__(710);
-const CpFileError = __webpack_require__(713);
+const makeDir = __webpack_require__(714);
+const pEvent = __webpack_require__(708);
+const CpFileError = __webpack_require__(711);
const stat = promisify(fs.stat);
const lstat = promisify(fs.lstat);
@@ -83617,7 +83533,7 @@ exports.copyFileSync = (source, destination, flags) => {
/***/ }),
-/* 716 */
+/* 714 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -83625,7 +83541,7 @@ exports.copyFileSync = (source, destination, flags) => {
const fs = __webpack_require__(134);
const path = __webpack_require__(4);
const {promisify} = __webpack_require__(112);
-const semver = __webpack_require__(717);
+const semver = __webpack_require__(715);
const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0');
@@ -83780,7 +83696,7 @@ module.exports.sync = (input, options) => {
/***/ }),
-/* 717 */
+/* 715 */
/***/ (function(module, exports) {
exports = module.exports = SemVer
@@ -85382,7 +85298,7 @@ function coerce (version, options) {
/***/ }),
-/* 718 */
+/* 716 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -85423,7 +85339,7 @@ module.exports = ProgressEmitter;
/***/ }),
-/* 719 */
+/* 717 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -85469,12 +85385,12 @@ exports.default = module.exports;
/***/ }),
-/* 720 */
+/* 718 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const pMap = __webpack_require__(721);
+const pMap = __webpack_require__(719);
const pFilter = async (iterable, filterer, options) => {
const values = await pMap(
@@ -85491,7 +85407,7 @@ module.exports.default = pFilter;
/***/ }),
-/* 721 */
+/* 719 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -85570,12 +85486,12 @@ module.exports.default = pMap;
/***/ }),
-/* 722 */
+/* 720 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-const NestedError = __webpack_require__(714);
+const NestedError = __webpack_require__(712);
class CpyError extends NestedError {
constructor(message, nested) {
diff --git a/packages/kbn-ui-framework/generator-kui/app/documentation.js b/packages/kbn-ui-framework/generator-kui/app/documentation.js
deleted file mode 100644
index 3cbc0263789c6..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/app/documentation.js
+++ /dev/null
@@ -1,56 +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.
- */
-
-const Generator = require('yeoman-generator');
-
-const documentationGenerator = require.resolve('../documentation/index.js');
-
-module.exports = class extends Generator {
- prompting() {
- return this.prompt([
- {
- message: 'What do you want to create?',
- name: 'fileType',
- type: 'list',
- choices: [
- {
- name: 'Page',
- value: 'documentation',
- },
- {
- name: 'Page demo',
- value: 'demo',
- },
- {
- name: 'Sandbox',
- value: 'sandbox',
- },
- ],
- },
- ]).then((answers) => {
- this.config = answers;
- });
- }
-
- writing() {
- this.composeWith(documentationGenerator, {
- fileType: this.config.fileType,
- });
- }
-};
diff --git a/packages/kbn-ui-framework/generator-kui/component/index.js b/packages/kbn-ui-framework/generator-kui/component/index.js
deleted file mode 100644
index 56c49fe6fa471..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/component/index.js
+++ /dev/null
@@ -1,156 +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.
- */
-
-const chalk = require('chalk');
-const { resolve } = require('path');
-const Generator = require('yeoman-generator');
-const utils = require('../utils');
-
-module.exports = class extends Generator {
- constructor(args, options) {
- super(args, options);
-
- this.fileType = options.fileType;
- }
-
- prompting() {
- return this.prompt([
- {
- message: "What's the name of this component? Use snake_case, please.",
- name: 'name',
- type: 'input',
- },
- {
- message: `Where do you want to create this component's files?`,
- type: 'input',
- name: 'path',
- default: resolve(__dirname, '../../src/components'),
- store: true,
- },
- {
- message: 'Does it need its own directory?',
- name: 'shouldMakeDirectory',
- type: 'confirm',
- default: true,
- },
- ]).then((answers) => {
- this.config = answers;
-
- if (!answers.name || !answers.name.trim()) {
- this.log.error('Sorry, please run this generator again and provide a component name.');
- process.exit(1);
- }
- });
- }
-
- writing() {
- const config = this.config;
-
- const writeComponent = (isStatelessFunction) => {
- const componentName = utils.makeComponentName(config.name);
- const cssClassName = utils.lowerCaseFirstLetter(componentName);
- const fileName = config.name;
-
- const path = utils.addDirectoryToPath(config.path, fileName, config.shouldMakeDirectory);
-
- const vars = (config.vars = {
- componentName,
- cssClassName,
- fileName,
- });
-
- const componentPath = (config.componentPath = `${path}/${fileName}.js`);
- const testPath = (config.testPath = `${path}/${fileName}.test.js`);
- const stylesPath = (config.stylesPath = `${path}/_${fileName}.scss`);
- config.stylesImportPath = `./_${fileName}.scss`;
-
- // If it needs its own directory then it will need a root index file too.
- if (this.config.shouldMakeDirectory) {
- this.fs.copyTpl(
- this.templatePath('_index.scss'),
- this.destinationPath(`${path}/_index.scss`),
- vars
- );
-
- this.fs.copyTpl(
- this.templatePath('index.js'),
- this.destinationPath(`${path}/index.js`),
- vars
- );
- }
-
- // Create component file.
- this.fs.copyTpl(
- isStatelessFunction
- ? this.templatePath('stateless_function.js')
- : this.templatePath('component.js'),
- this.destinationPath(componentPath),
- vars
- );
-
- // Create component test file.
- this.fs.copyTpl(this.templatePath('test.js'), this.destinationPath(testPath), vars);
-
- // Create component styles file.
- this.fs.copyTpl(this.templatePath('_component.scss'), this.destinationPath(stylesPath), vars);
- };
-
- switch (this.fileType) {
- case 'component':
- writeComponent();
- break;
-
- case 'function':
- writeComponent(true);
- break;
- }
- }
-
- end() {
- const showImportComponentSnippet = () => {
- const componentName = this.config.vars.componentName;
-
- this.log(chalk.white(`\n// Export component (e.. from component's index.js).`));
- this.log(
- `${chalk.magenta('export')} {\n` +
- ` ${componentName},\n` +
- `} ${chalk.magenta('from')} ${chalk.cyan(`'./${this.config.name}'`)};`
- );
-
- this.log(chalk.white('\n// Import styles.'));
- this.log(`${chalk.magenta('@import')} ${chalk.cyan(`'${this.config.name}'`)};`);
-
- this.log(chalk.white('\n// Import component styles into the root index.scss.'));
- this.log(`${chalk.magenta('@import')} ${chalk.cyan(`'${this.config.name}/index'`)};`);
- };
-
- this.log('------------------------------------------------');
- this.log(chalk.bold('Handy snippets:'));
- switch (this.fileType) {
- case 'component':
- showImportComponentSnippet();
- break;
-
- case 'function':
- showImportComponentSnippet();
- break;
- }
- this.log('------------------------------------------------');
- }
-};
diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/_component.scss b/packages/kbn-ui-framework/generator-kui/component/templates/_component.scss
deleted file mode 100644
index 668cabce61327..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/component/templates/_component.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.<%= cssClassName %> {
-
-}
diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/_index.scss b/packages/kbn-ui-framework/generator-kui/component/templates/_index.scss
deleted file mode 100644
index 088dee9874946..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/component/templates/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import '<%= fileName %>';
diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/component.js b/packages/kbn-ui-framework/generator-kui/component/templates/component.js
deleted file mode 100644
index 31e362222471a..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/component/templates/component.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React, {
- Component,
-} from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-
-export class <%= componentName %> extends Component {
- static propTypes = {
- children: PropTypes.node,
- className: PropTypes.string,
- }
-
- constructor(props) {
- super(props);
- }
-
- render() {
- const {
- children,
- className,
- ...rest
- } = this.props;
-
- const classes = classNames('<%= cssClassName %>', className);
-
- return (
-
- {children}
-
- );
- }
-}
diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/index.js b/packages/kbn-ui-framework/generator-kui/component/templates/index.js
deleted file mode 100644
index 1da6deaa79d9a..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/component/templates/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export {
- <%= componentName %>,
-} from './<%= fileName %>';
diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/stateless_function.js b/packages/kbn-ui-framework/generator-kui/component/templates/stateless_function.js
deleted file mode 100644
index 7fcbf0c19d728..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/component/templates/stateless_function.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-
-export const <%= componentName %> = ({
- children,
- className,
- ...rest
-}) => {
- const classes = classNames('<%= cssClassName %>', className);
-
- return (
-
- {children}
-
- );
-};
-
-<%= componentName %>.propTypes = {
- children: PropTypes.node,
- className: PropTypes.string,
-};
diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/test.js b/packages/kbn-ui-framework/generator-kui/component/templates/test.js
deleted file mode 100644
index 4f384d6c2d3aa..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/component/templates/test.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-import { render } from 'enzyme';
-import { requiredProps } from '../../test/required_props';
-
-import { <%= componentName %> } from './<%= fileName %>';
-
-describe('<%= componentName %>', () => {
- test('is rendered', () => {
- const component = render(
- <<%= componentName %> {...requiredProps} />
- );
-
- expect(component)
- .toMatchSnapshot();
- });
-});
diff --git a/packages/kbn-ui-framework/generator-kui/documentation/index.js b/packages/kbn-ui-framework/generator-kui/documentation/index.js
deleted file mode 100644
index 03f8d5813b251..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/documentation/index.js
+++ /dev/null
@@ -1,238 +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.
- */
-
-const chalk = require('chalk');
-const { resolve } = require('path');
-const Generator = require('yeoman-generator');
-const utils = require('../utils');
-
-const DOCUMENTATION_PAGE_PATH = resolve(__dirname, '../../doc_site/src/views');
-
-module.exports = class extends Generator {
- constructor(args, options) {
- super(args, options);
-
- this.fileType = options.fileType;
- }
-
- prompting() {
- const prompts = [
- {
- message: "What's the name of the component you're documenting? Use snake_case, please.",
- name: 'name',
- type: 'input',
- store: true,
- },
- ];
-
- if (this.fileType === 'demo') {
- prompts.push({
- message: `What's the name of the directory this demo should go in? (Within ${DOCUMENTATION_PAGE_PATH}). Use snake_case, please.`,
- name: 'folderName',
- type: 'input',
- store: true,
- default: (answers) => answers.name,
- });
-
- prompts.push({
- message: 'What would you like to name this demo? Use snake_case, please.',
- name: 'demoName',
- type: 'input',
- store: true,
- });
- }
-
- return this.prompt(prompts).then((answers) => {
- this.config = answers;
- });
- }
-
- writing() {
- const config = this.config;
-
- const writeDocumentationPage = () => {
- const componentExampleName = utils.makeComponentName(config.name, false);
- const componentExamplePrefix = utils.lowerCaseFirstLetter(componentExampleName);
- const fileName = config.name;
-
- const path = DOCUMENTATION_PAGE_PATH;
-
- const vars = (config.documentationVars = {
- componentExampleName,
- componentExamplePrefix,
- fileName,
- });
-
- const documentationPagePath = (config.documentationPagePath = `${path}/${config.name}/${config.name}_example.js`);
-
- this.fs.copyTpl(
- this.templatePath('documentation_page.js'),
- this.destinationPath(documentationPagePath),
- vars
- );
- };
-
- const writeDocumentationPageDemo = (fileName, folderName) => {
- const componentExampleName = utils.makeComponentName(fileName, false);
- const componentExamplePrefix = utils.lowerCaseFirstLetter(componentExampleName);
- const componentName = utils.makeComponentName(config.name);
-
- const path = DOCUMENTATION_PAGE_PATH;
-
- const vars = (config.documentationVars = {
- componentExampleName,
- componentExamplePrefix,
- componentName,
- fileName,
- });
-
- const documentationPageDemoPath = (config.documentationPageDemoPath = `${path}/${folderName}/${fileName}.js`);
-
- this.fs.copyTpl(
- this.templatePath('documentation_page_demo.js'),
- this.destinationPath(documentationPageDemoPath),
- vars
- );
- };
-
- const writeSandbox = () => {
- const fileName = config.name;
- const componentExampleName = utils.makeComponentName(fileName, false);
-
- const path = DOCUMENTATION_PAGE_PATH;
-
- const vars = (config.documentationVars = {
- componentExampleName,
- fileName,
- });
-
- const sandboxPath = (config.documentationPageDemoPath = `${path}/${config.name}/${fileName}`);
-
- this.fs.copyTpl(
- this.templatePath('documentation_sandbox.html'),
- this.destinationPath(`${sandboxPath}_sandbox.html`)
- );
-
- this.fs.copyTpl(
- this.templatePath('documentation_sandbox.js'),
- this.destinationPath(`${sandboxPath}_sandbox.js`),
- vars
- );
- };
-
- switch (this.fileType) {
- case 'documentation':
- writeDocumentationPage();
- writeDocumentationPageDemo(config.name, config.name);
- break;
-
- case 'demo':
- writeDocumentationPageDemo(config.demoName, config.folderName);
- break;
-
- case 'sandbox':
- writeSandbox();
- break;
- }
- }
-
- end() {
- const showImportDemoSnippet = () => {
- const {
- componentExampleName,
- componentExamplePrefix,
- fileName,
- } = this.config.documentationVars;
-
- this.log(chalk.white('\n// Import demo into example.'));
- this.log(
- `${chalk.magenta('import')} ${componentExampleName} from ${chalk.cyan(
- `'./${fileName}'`
- )};\n` +
- `${chalk.magenta('const')} ${componentExamplePrefix}Source = require(${chalk.cyan(
- `'!!raw-loader!./${fileName}'`
- )});\n` +
- `${chalk.magenta(
- 'const'
- )} ${componentExamplePrefix}Html = renderToHtml(${componentExampleName});`
- );
-
- this.log(chalk.white('\n// Render demo.'));
- this.log(
- `\n` +
- ` \n` +
- ` Description needed: how to use the ${componentExampleName} component.\n` +
- ` \n` +
- `\n` +
- ` \n` +
- ` <${componentExampleName} />\n` +
- ` \n` +
- ` \n`
- );
- };
-
- const showImportRouteSnippet = (suffix, appendToRoute) => {
- const { componentExampleName, fileName } = this.config.documentationVars;
-
- this.log(chalk.white('\n// Import example into routes.js.'));
- this.log(
- `${chalk.magenta('import')} ${componentExampleName}${suffix}\n` +
- ` ${chalk.magenta('from')} ${chalk.cyan(
- `'../../views/${fileName}/${fileName}_${suffix.toLowerCase()}'`
- )};`
- );
-
- this.log(chalk.white('\n// Import route definition into routes.js.'));
- this.log(
- `{\n` +
- ` name: ${chalk.cyan(`'${componentExampleName}${appendToRoute ? suffix : ''}'`)},\n` +
- ` component: ${componentExampleName}${suffix},\n` +
- ` hasReact: ${chalk.magenta('true')},\n` +
- `}`
- );
- };
-
- this.log('------------------------------------------------');
- this.log(chalk.bold('Import snippets:'));
-
- switch (this.fileType) {
- case 'documentation':
- showImportRouteSnippet('Example');
- break;
-
- case 'demo':
- showImportDemoSnippet();
- break;
-
- case 'sandbox':
- showImportRouteSnippet('Sandbox', true);
- break;
- }
- this.log('------------------------------------------------');
- }
-};
diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page.js b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page.js
deleted file mode 100644
index df45099bb9c64..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/* eslint-disable import/no-duplicates */
-
-import React from 'react';
-
-import { renderToHtml } from '../../services';
-
-import {
- GuideCode,
- GuideDemo,
- GuidePage,
- GuideSection,
- GuideSectionTypes,
- GuideText,
-} from '../../components';
-
-import <%= componentExampleName %> from './<%= fileName %>';
-import <%= componentExamplePrefix %>Source from '!!raw-loader!./<%= fileName %>'; // eslint-disable-line import/default
-const <%= componentExamplePrefix %>Html = renderToHtml(<%= componentExampleName %>);
-
-export default props => (
-
- Source,
- }, {
- type: GuideSectionTypes.HTML,
- code: <%= componentExamplePrefix %>Html,
- }]}
- >
-
- Description needed: how to use the <%= componentExampleName %> component.
-
-
-
- <<%= componentExampleName %> />
-
-
-
-);
diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page_demo.js b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page_demo.js
deleted file mode 100644
index 645f194bb3c7b..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page_demo.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-
-import {
- <%= componentName %>,
-} from '../../../../components';
-
-export default () => (
- <<%= componentName %>>
-
- <%= componentName %>>
-);
diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.html b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.html
deleted file mode 100644
index 2515d47beb72f..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.html
+++ /dev/null
@@ -1 +0,0 @@
-Do whatever you want here!
diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.js b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.js
deleted file mode 100644
index 6dd661601b891..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react';
-
-import {
- GuideDemo,
- GuideSandbox,
- GuideSandboxCodeToggle,
- GuideSectionTypes,
-} from '../../components';
-
-import html from './<%= fileName %>_sandbox.html';
-
-export default props => (
-
-
-
-
-
-);
diff --git a/packages/kbn-ui-framework/generator-kui/utils.js b/packages/kbn-ui-framework/generator-kui/utils.js
deleted file mode 100644
index 0f7b910451767..0000000000000
--- a/packages/kbn-ui-framework/generator-kui/utils.js
+++ /dev/null
@@ -1,56 +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.
- */
-
-function makeComponentName(str, usePrefix = true) {
- const words = str.split('_');
-
- const componentName = words
- .map(function (word) {
- return upperCaseFirstLetter(word);
- })
- .join('');
-
- return `${usePrefix ? 'Kui' : ''}${componentName}`;
-}
-
-function lowerCaseFirstLetter(str) {
- return str.replace(/\w\S*/g, function (txt) {
- return txt.charAt(0).toLowerCase() + txt.substr(1);
- });
-}
-
-function upperCaseFirstLetter(str) {
- return str.replace(/\w\S*/g, function (txt) {
- return txt.charAt(0).toUpperCase() + txt.substr(1);
- });
-}
-
-function addDirectoryToPath(path, dirName, shouldMakeDirectory) {
- if (shouldMakeDirectory) {
- return path + '/' + dirName;
- }
- return path;
-}
-
-module.exports = {
- makeComponentName: makeComponentName,
- lowerCaseFirstLetter: lowerCaseFirstLetter,
- upperCaseFirstLetter: upperCaseFirstLetter,
- addDirectoryToPath: addDirectoryToPath,
-};
diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json
index 9e3adf3f61f81..2b66f684b0c5d 100644
--- a/packages/kbn-ui-framework/package.json
+++ b/packages/kbn-ui-framework/package.json
@@ -5,9 +5,7 @@
"scripts": {
"build": "../../node_modules/.bin/grunt prodBuild",
"docSiteStart": "../../node_modules/.bin/grunt docSiteStart",
- "docSiteBuild": "../../node_modules/.bin/grunt docSiteBuild",
- "createComponent": "../../node_modules/.bin/yo ./generator-kui/app/component.js",
- "documentComponent": "../../node_modules/.bin/yo ./generator-kui/app/documentation.js"
+ "docSiteBuild": "../../node_modules/.bin/grunt docSiteBuild"
},
"kibana": {
"build": {
diff --git a/rfcs/text/0013_saved_object_migrations.md b/rfcs/text/0013_saved_object_migrations.md
index c5069625cb8a6..1a0967d110d06 100644
--- a/rfcs/text/0013_saved_object_migrations.md
+++ b/rfcs/text/0013_saved_object_migrations.md
@@ -212,39 +212,68 @@ Note:
If none of the aliases exists, this is a new Elasticsearch cluster and no
migrations are necessary. Create the `.kibana_7.10.0_001` index with the
following aliases: `.kibana_current` and `.kibana_7.10.0`.
-2. If `.kibana_current` and `.kibana_7.10.0` both exists and are pointing to the same index this version's migration has already been completed.
+2. If the source is a < v6.5 `.kibana` index or < 7.4 `.kibana_task_manager`
+ index prepare the legacy index for a migration:
+ 1. Mark the legacy index as read-only and wait for all in-flight operations to drain (requires https://github.com/elastic/elasticsearch/pull/58094). This prevents any further writes from outdated nodes. Assuming this API is similar to the existing `//_close` API, we expect to receive `"acknowledged" : true` and `"shards_acknowledged" : true`. If all shards don’t acknowledge within the timeout, retry the operation until it succeeds.
+ 2. Clone the legacy index into a new index which has writes enabled. Use a fixed index name i.e `.kibana_pre6.5.0_001` or `.kibana_task_manager_pre7.4.0_001`. `POST /.kibana/_clone/.kibana_pre6.5.0_001?wait_for_active_shards=all {"settings": {"index.blocks.write": false}}`. Ignore errors if the clone already exists. Ignore errors if the legacy source doesn't exist.
+ 3. Wait for the cloning to complete `GET /_cluster/health/.kibana_pre6.5.0_001?wait_for_status=green&timeout=60s` If cloning doesn’t complete within the 60s timeout, log a warning for visibility and poll again.
+ 4. Apply the `convertToAlias` script if defined `POST /.kibana_pre6.5.0_001/_update_by_query?conflicts=proceed {"script": {...}}`. The `convertToAlias` script will have to be idempotent, preferably setting `ctx.op="noop"` on subsequent runs to avoid unecessary writes.
+ 5. Delete the legacy index and replace it with an alias of the same name
+ ```
+ POST /_aliases
+ {
+ "actions" : [
+ { "add": { "index": ".kibana_pre6.5.0_001", "alias": ".kibana" } },
+ { "remove_index": { "index": ".kibana" } }
+ ]
+ }
+ ```.
+ Unlike the delete index API, the `remove_index` action will fail if
+ provided with an _alias_. Ignore "The provided expression [.kibana]
+ matches an alias, specify the corresponding concrete indices instead."
+ or "index_not_found_exception" errors. These actions are applied
+ atomically so that other Kibana instances will always see either a
+ `.kibana` index or an alias, but never neither.
+ 6. Use the cloned `.kibana_pre6.5.0_001` as the source for the rest of the migration algorithm.
+3. If `.kibana_current` and `.kibana_7.10.0` both exists and are pointing to the same index this version's migration has already been completed.
1. Because the same version can have plugins enabled at any point in time,
perform the mappings update in step (6) and migrate outdated documents
with step (7).
2. Skip to step (9) to start serving traffic.
-3. Fail the migration if:
+4. Fail the migration if:
1. `.kibana_current` is pointing to an index that belongs to a later version of Kibana .e.g. `.kibana_7.12.0_001`
2. (Only in 8.x) The source index contains documents that belong to an unknown Saved Object type (from a disabled plugin). Log an error explaining that the plugin that created these documents needs to be enabled again or that these objects should be deleted. See section (4.2.1.4).
-4. Mark the source index as read-only and wait for all in-flight operations to drain (requires https://github.com/elastic/elasticsearch/pull/58094). This prevents any further writes from outdated nodes. Assuming this API is similar to the existing `//_close` API, we expect to receive `"acknowledged" : true` and `"shards_acknowledged" : true`. If all shards don’t acknowledge within the timeout, retry the operation until it succeeds.
-5. Clone the source index into a new target index which has writes enabled. All nodes on the same version will use the same fixed index name e.g. `.kibana_7.10.0_001`. The `001` postfix isn't used by Kibana, but allows for re-indexing an index should this be required by an Elasticsearch upgrade. E.g. re-index `.kibana_7.10.0_001` into `.kibana_7.10.0_002` and point the `.kibana_7.10.0` alias to `.kibana_7.10.0_002`.
+5. Mark the source index as read-only and wait for all in-flight operations to drain (requires https://github.com/elastic/elasticsearch/pull/58094). This prevents any further writes from outdated nodes. Assuming this API is similar to the existing `//_close` API, we expect to receive `"acknowledged" : true` and `"shards_acknowledged" : true`. If all shards don’t acknowledge within the timeout, retry the operation until it succeeds.
+6. Clone the source index into a new target index which has writes enabled. All nodes on the same version will use the same fixed index name e.g. `.kibana_7.10.0_001`. The `001` postfix isn't used by Kibana, but allows for re-indexing an index should this be required by an Elasticsearch upgrade. E.g. re-index `.kibana_7.10.0_001` into `.kibana_7.10.0_002` and point the `.kibana_7.10.0` alias to `.kibana_7.10.0_002`.
1. `POST /.kibana_n/_clone/.kibana_7.10.0_001?wait_for_active_shards=all {"settings": {"index.blocks.write": false}}`. Ignore errors if the clone already exists.
2. Wait for the cloning to complete `GET /_cluster/health/.kibana_7.10.0_001?wait_for_status=green&timeout=60s` If cloning doesn’t complete within the 60s timeout, log a warning for visibility and poll again.
-6. Update the mappings of the target index
+7. Update the mappings of the target index
1. Retrieve the existing mappings including the `migrationMappingPropertyHashes` metadata.
2. Update the mappings with `PUT /.kibana_7.10.0_001/_mapping`. The API deeply merges any updates so this won't remove the mappings of any plugins that were enabled in a previous version but are now disabled.
3. Ensure that fields are correctly indexed using the target index's latest mappings `POST /.kibana_7.10.0_001/_update_by_query?conflicts=proceed`. In the future we could optimize this query by only targeting documents:
1. That belong to a known saved object type.
2. Which don't have outdated migrationVersion numbers since these will be transformed anyway.
3. That belong to a type whose mappings were changed by comparing the `migrationMappingPropertyHashes`. (Metadata, unlike the mappings isn't commutative, so there is a small chance that the metadata hashes do not accurately reflect the latest mappings, however, this will just result in an less efficient query).
-7. Transform documents by reading batches of outdated documents from the target index then transforming and updating them with optimistic concurrency control.
+8. Transform documents by reading batches of outdated documents from the target index then transforming and updating them with optimistic concurrency control.
1. Ignore any version conflict errors.
2. If a document transform throws an exception, add the document to a failure list and continue trying to transform all other documents. If any failures occured, log the complete list of documents that failed to transform. Fail the migration.
-8. Mark the migration as complete by doing a single atomic operation (requires https://github.com/elastic/elasticsearch/pull/58100) that:
- 1. Checks that `.kibana-current` alias is still pointing to the source index
- 2. Points the `.kibana-7.10.0` and `.kibana_current` aliases to the target index.
- 3. If this fails with a "required alias [.kibana_current] does not exist" error fetch `.kibana_current` again:
+9. Mark the migration as complete by doing a single atomic operation (requires https://github.com/elastic/elasticsearch/pull/58100) that:
+ 3. Checks that `.kibana_current` alias is still pointing to the source index
+ 4. Points the `.kibana_7.10.0` and `.kibana_current` aliases to the target index.
+ 5. If this fails with a "required alias [.kibana_current] does not exist" error fetch `.kibana_current` again:
1. If `.kibana_current` is _not_ pointing to our target index fail the migration.
2. If `.kibana_current` is pointing to our target index the migration has succeeded and we can proceed to step (9).
-9. Start serving traffic.
+10. Start serving traffic.
+
+This algorithm shares a weakness with our existing migration algorithm
+(since v7.4). When the task manager index gets reindexed a reindex script is
+applied. Because we delete the original task manager index there is no way to
+rollback a failed task manager migration without a snapshot.
Together with the limitations, this algorithm ensures that migrations are
idempotent. If two nodes are started simultaneously, both of them will start
-transforming documents in that version's target index, but because migrations are idempotent, it doesn’t matter which node’s writes win.
+transforming documents in that version's target index, but because migrations
+are idempotent, it doesn’t matter which node’s writes win.
In the future, this algorithm could enable (2.6) "read-only functionality during the downtime window" but this is outside of the scope of this RFC.
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index 6988a3211fa12..48187fe465392 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -37,6 +37,7 @@ export class DocLinksService {
ELASTIC_WEBSITE_URL,
links: {
dashboard: {
+ guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`,
drilldowns: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html`,
drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#trigger-picker`,
urlDrilldownTemplateSyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#templating`,
@@ -134,6 +135,8 @@ export class DocLinksService {
visualize: {
guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/visualize.html`,
timelionDeprecation: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html#timelion-deprecation`,
+ lens: `${ELASTIC_WEBSITE_URL}what-is/kibana-lens`,
+ maps: `${ELASTIC_WEBSITE_URL}maps`,
},
},
});
@@ -146,6 +149,7 @@ export interface DocLinksStart {
readonly ELASTIC_WEBSITE_URL: string;
readonly links: {
readonly dashboard: {
+ readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index fd2d943cab9d2..781a50f849e24 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -460,6 +460,7 @@ export interface DocLinksStart {
// (undocumented)
readonly links: {
readonly dashboard: {
+ readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts
index 333f5caf72525..a8c5df8d64630 100644
--- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts
+++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts
@@ -21,28 +21,64 @@
import { esKuery } from '../../../es_query';
type KueryNode = any;
-import { typeRegistryMock } from '../../../saved_objects_type_registry.mock';
+import { SavedObjectTypeRegistry } from '../../../saved_objects_type_registry';
import { ALL_NAMESPACES_STRING } from '../utils';
import { getQueryParams, getClauseForReference } from './query_params';
-const registry = typeRegistryMock.create();
+const registerTypes = (registry: SavedObjectTypeRegistry) => {
+ registry.registerType({
+ name: 'pending',
+ hidden: false,
+ namespaceType: 'single',
+ mappings: {
+ properties: { title: { type: 'text' } },
+ },
+ management: {
+ defaultSearchField: 'title',
+ },
+ });
-const MAPPINGS = {
- properties: {
- pending: { properties: { title: { type: 'text' } } },
- saved: {
+ registry.registerType({
+ name: 'saved',
+ hidden: false,
+ namespaceType: 'single',
+ mappings: {
properties: {
title: { type: 'text', fields: { raw: { type: 'keyword' } } },
obj: { properties: { key1: { type: 'text' } } },
},
},
- // mock registry returns isMultiNamespace=true for 'shared' type
- shared: { properties: { name: { type: 'keyword' } } },
- // mock registry returns isNamespaceAgnostic=true for 'global' type
- global: { properties: { name: { type: 'keyword' } } },
- },
+ management: {
+ defaultSearchField: 'title',
+ },
+ });
+
+ registry.registerType({
+ name: 'shared',
+ hidden: false,
+ namespaceType: 'multiple',
+ mappings: {
+ properties: { name: { type: 'keyword' } },
+ },
+ management: {
+ defaultSearchField: 'name',
+ },
+ });
+
+ registry.registerType({
+ name: 'global',
+ hidden: false,
+ namespaceType: 'agnostic',
+ mappings: {
+ properties: { name: { type: 'keyword' } },
+ },
+ management: {
+ defaultSearchField: 'name',
+ },
+ });
};
-const ALL_TYPES = Object.keys(MAPPINGS.properties);
+
+const ALL_TYPES = ['pending', 'saved', 'shared', 'global'];
// get all possible subsets (combination) of all types
const ALL_TYPE_SUBSETS = ALL_TYPES.reduce(
(subsets, value) => subsets.concat(subsets.map((set) => [...set, value])),
@@ -51,48 +87,53 @@ const ALL_TYPE_SUBSETS = ALL_TYPES.reduce(
.filter((x) => x.length) // exclude empty set
.map((x) => (x.length === 1 ? x[0] : x)); // if a subset is a single string, destructure it
-const createTypeClause = (type: string, namespaces?: string[]) => {
- if (registry.isMultiNamespace(type)) {
- const array = [...(namespaces ?? ['default']), ALL_NAMESPACES_STRING];
- return {
- bool: {
- must: expect.arrayContaining([{ terms: { namespaces: array } }]),
- must_not: [{ exists: { field: 'namespace' } }],
- },
- };
- } else if (registry.isSingleNamespace(type)) {
- const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? [];
- const should: any = [];
- if (nonDefaultNamespaces.length > 0) {
- should.push({ terms: { namespace: nonDefaultNamespaces } });
- }
- if (namespaces?.includes('default')) {
- should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } });
- }
- return {
- bool: {
- must: [{ term: { type } }],
- should: expect.arrayContaining(should),
- minimum_should_match: 1,
- must_not: [{ exists: { field: 'namespaces' } }],
- },
- };
- }
- // isNamespaceAgnostic
- return {
- bool: expect.objectContaining({
- must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }],
- }),
- };
-};
-
/**
* Note: these tests cases are defined in the order they appear in the source code, for readability's sake
*/
describe('#getQueryParams', () => {
- const mappings = MAPPINGS;
+ let registry: SavedObjectTypeRegistry;
type Result = ReturnType;
+ beforeEach(() => {
+ registry = new SavedObjectTypeRegistry();
+ registerTypes(registry);
+ });
+
+ const createTypeClause = (type: string, namespaces?: string[]) => {
+ if (registry.isMultiNamespace(type)) {
+ const array = [...(namespaces ?? ['default']), ALL_NAMESPACES_STRING];
+ return {
+ bool: {
+ must: expect.arrayContaining([{ terms: { namespaces: array } }]),
+ must_not: [{ exists: { field: 'namespace' } }],
+ },
+ };
+ } else if (registry.isSingleNamespace(type)) {
+ const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? [];
+ const should: any = [];
+ if (nonDefaultNamespaces.length > 0) {
+ should.push({ terms: { namespace: nonDefaultNamespaces } });
+ }
+ if (namespaces?.includes('default')) {
+ should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } });
+ }
+ return {
+ bool: {
+ must: [{ term: { type } }],
+ should: expect.arrayContaining(should),
+ minimum_should_match: 1,
+ must_not: [{ exists: { field: 'namespaces' } }],
+ },
+ };
+ }
+ // isNamespaceAgnostic
+ return {
+ bool: expect.objectContaining({
+ must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }],
+ }),
+ };
+ };
+
describe('kueryNode filter clause', () => {
const expectResult = (result: Result, expected: any) => {
expect(result.query.bool.filter).toEqual(expect.arrayContaining([expected]));
@@ -100,13 +141,13 @@ describe('#getQueryParams', () => {
describe('`kueryNode` parameter', () => {
it('does not include the clause when `kueryNode` is not specified', () => {
- const result = getQueryParams({ mappings, registry, kueryNode: undefined });
+ const result = getQueryParams({ registry, kueryNode: undefined });
expect(result.query.bool.filter).toHaveLength(1);
});
it('includes the specified Kuery clause', () => {
const test = (kueryNode: KueryNode) => {
- const result = getQueryParams({ mappings, registry, kueryNode });
+ const result = getQueryParams({ registry, kueryNode });
const expected = esKuery.toElasticsearchQuery(kueryNode);
expect(result.query.bool.filter).toHaveLength(2);
expectResult(result, expected);
@@ -165,7 +206,6 @@ describe('#getQueryParams', () => {
it('does not include the clause when `hasReference` is not specified', () => {
const result = getQueryParams({
- mappings,
registry,
hasReference: undefined,
});
@@ -176,7 +216,6 @@ describe('#getQueryParams', () => {
it('creates a should clause for specified reference when operator is `OR`', () => {
const hasReference = { id: 'foo', type: 'bar' };
const result = getQueryParams({
- mappings,
registry,
hasReference,
hasReferenceOperator: 'OR',
@@ -192,7 +231,6 @@ describe('#getQueryParams', () => {
it('creates a must clause for specified reference when operator is `AND`', () => {
const hasReference = { id: 'foo', type: 'bar' };
const result = getQueryParams({
- mappings,
registry,
hasReference,
hasReferenceOperator: 'AND',
@@ -210,7 +248,6 @@ describe('#getQueryParams', () => {
{ id: 'hello', type: 'dolly' },
];
const result = getQueryParams({
- mappings,
registry,
hasReference,
hasReferenceOperator: 'OR',
@@ -229,7 +266,6 @@ describe('#getQueryParams', () => {
{ id: 'hello', type: 'dolly' },
];
const result = getQueryParams({
- mappings,
registry,
hasReference,
hasReferenceOperator: 'AND',
@@ -244,7 +280,6 @@ describe('#getQueryParams', () => {
it('defaults to `OR` when operator is not specified', () => {
const hasReference = { id: 'foo', type: 'bar' };
const result = getQueryParams({
- mappings,
registry,
hasReference,
});
@@ -278,14 +313,13 @@ describe('#getQueryParams', () => {
};
it('searches for all known types when `type` is not specified', () => {
- const result = getQueryParams({ mappings, registry, type: undefined });
+ const result = getQueryParams({ registry, type: undefined });
expectResult(result, ...ALL_TYPES);
});
it('searches for specified type/s', () => {
const test = (typeOrTypes: string | string[]) => {
const result = getQueryParams({
- mappings,
registry,
type: typeOrTypes,
});
@@ -309,18 +343,17 @@ describe('#getQueryParams', () => {
const test = (namespaces?: string[]) => {
for (const typeOrTypes of ALL_TYPE_SUBSETS) {
- const result = getQueryParams({ mappings, registry, type: typeOrTypes, namespaces });
+ const result = getQueryParams({ registry, type: typeOrTypes, namespaces });
const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
expectResult(result, ...types.map((x) => createTypeClause(x, namespaces)));
}
// also test with no specified type/s
- const result = getQueryParams({ mappings, registry, type: undefined, namespaces });
+ const result = getQueryParams({ registry, type: undefined, namespaces });
expectResult(result, ...ALL_TYPES.map((x) => createTypeClause(x, namespaces)));
};
it('normalizes and deduplicates provided namespaces', () => {
const result = getQueryParams({
- mappings,
registry,
search: '*',
namespaces: ['foo', '*', 'foo', 'bar', 'default'],
@@ -360,7 +393,6 @@ describe('#getQueryParams', () => {
it('supersedes `type` and `namespaces` parameters', () => {
const result = getQueryParams({
- mappings,
registry,
type: ['pending', 'saved', 'shared', 'global'],
namespaces: ['foo', 'bar', 'default'],
@@ -381,148 +413,266 @@ describe('#getQueryParams', () => {
});
});
- describe('search clause (query.bool.must.simple_query_string)', () => {
- const search = 'foo*';
+ describe('search clause (query.bool)', () => {
+ describe('when using simple search (query.bool.must.simple_query_string)', () => {
+ const search = 'foo';
- const expectResult = (result: Result, sqsClause: any) => {
- expect(result.query.bool.must).toEqual([{ simple_query_string: sqsClause }]);
- };
+ const expectResult = (result: Result, sqsClause: any) => {
+ expect(result.query.bool.must).toEqual([{ simple_query_string: sqsClause }]);
+ };
- describe('`search` parameter', () => {
- it('does not include clause when `search` is not specified', () => {
- const result = getQueryParams({
- mappings,
- registry,
- search: undefined,
+ describe('`search` parameter', () => {
+ it('does not include clause when `search` is not specified', () => {
+ const result = getQueryParams({
+ registry,
+ search: undefined,
+ });
+ expect(result.query.bool.must).toBeUndefined();
});
- expect(result.query.bool.must).toBeUndefined();
- });
- it('creates a clause with query for specified search', () => {
- const result = getQueryParams({
- mappings,
- registry,
- search,
+ it('creates a clause with query for specified search', () => {
+ const result = getQueryParams({
+ registry,
+ search,
+ });
+ expectResult(result, expect.objectContaining({ query: search }));
});
- expectResult(result, expect.objectContaining({ query: search }));
});
- });
- describe('`searchFields` and `rootSearchFields` parameters', () => {
- const getExpectedFields = (searchFields: string[], typeOrTypes: string | string[]) => {
- const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
- return searchFields.map((x) => types.map((y) => `${y}.${x}`)).flat();
- };
+ describe('`searchFields` and `rootSearchFields` parameters', () => {
+ const getExpectedFields = (searchFields: string[], typeOrTypes: string | string[]) => {
+ const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
+ return searchFields.map((x) => types.map((y) => `${y}.${x}`)).flat();
+ };
- const test = ({
- searchFields,
- rootSearchFields,
- }: {
- searchFields?: string[];
- rootSearchFields?: string[];
- }) => {
- for (const typeOrTypes of ALL_TYPE_SUBSETS) {
+ const test = ({
+ searchFields,
+ rootSearchFields,
+ }: {
+ searchFields?: string[];
+ rootSearchFields?: string[];
+ }) => {
+ for (const typeOrTypes of ALL_TYPE_SUBSETS) {
+ const result = getQueryParams({
+ registry,
+ type: typeOrTypes,
+ search,
+ searchFields,
+ rootSearchFields,
+ });
+ let fields = rootSearchFields || [];
+ if (searchFields) {
+ fields = fields.concat(getExpectedFields(searchFields, typeOrTypes));
+ }
+ expectResult(result, expect.objectContaining({ fields }));
+ }
+ // also test with no specified type/s
const result = getQueryParams({
- mappings,
registry,
- type: typeOrTypes,
+ type: undefined,
search,
searchFields,
rootSearchFields,
});
let fields = rootSearchFields || [];
if (searchFields) {
- fields = fields.concat(getExpectedFields(searchFields, typeOrTypes));
+ fields = fields.concat(getExpectedFields(searchFields, ALL_TYPES));
}
expectResult(result, expect.objectContaining({ fields }));
- }
- // also test with no specified type/s
- const result = getQueryParams({
- mappings,
- registry,
- type: undefined,
- search,
- searchFields,
- rootSearchFields,
+ };
+
+ it('throws an error if a raw search field contains a "." character', () => {
+ expect(() =>
+ getQueryParams({
+ registry,
+ type: undefined,
+ search,
+ searchFields: undefined,
+ rootSearchFields: ['foo', 'bar.baz'],
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"rootSearchFields entry \\"bar.baz\\" is invalid: cannot contain \\".\\" character"`
+ );
});
- let fields = rootSearchFields || [];
- if (searchFields) {
- fields = fields.concat(getExpectedFields(searchFields, ALL_TYPES));
- }
- expectResult(result, expect.objectContaining({ fields }));
- };
- it('throws an error if a raw search field contains a "." character', () => {
- expect(() =>
- getQueryParams({
- mappings,
+ it('includes lenient flag and all fields when `searchFields` and `rootSearchFields` are not specified', () => {
+ const result = getQueryParams({
registry,
- type: undefined,
search,
searchFields: undefined,
- rootSearchFields: ['foo', 'bar.baz'],
- })
- ).toThrowErrorMatchingInlineSnapshot(
- `"rootSearchFields entry \\"bar.baz\\" is invalid: cannot contain \\".\\" character"`
- );
+ rootSearchFields: undefined,
+ });
+ expectResult(result, expect.objectContaining({ lenient: true, fields: ['*'] }));
+ });
+
+ it('includes specified search fields for appropriate type/s', () => {
+ test({ searchFields: ['title'] });
+ });
+
+ it('supports boosting', () => {
+ test({ searchFields: ['title^3'] });
+ });
+
+ it('supports multiple search fields', () => {
+ test({ searchFields: ['title, title.raw'] });
+ });
+
+ it('includes specified raw search fields', () => {
+ test({ rootSearchFields: ['_id'] });
+ });
+
+ it('supports multiple raw search fields', () => {
+ test({ rootSearchFields: ['_id', 'originId'] });
+ });
+
+ it('supports search fields and raw search fields', () => {
+ test({ searchFields: ['title'], rootSearchFields: ['_id'] });
+ });
});
- it('includes lenient flag and all fields when `searchFields` and `rootSearchFields` are not specified', () => {
- const result = getQueryParams({
- mappings,
+ describe('`defaultSearchOperator` parameter', () => {
+ it('does not include default_operator when `defaultSearchOperator` is not specified', () => {
+ const result = getQueryParams({
+ registry,
+ search,
+ defaultSearchOperator: undefined,
+ });
+ expectResult(
+ result,
+ expect.not.objectContaining({ default_operator: expect.anything() })
+ );
+ });
+
+ it('includes specified default operator', () => {
+ const defaultSearchOperator = 'AND';
+ const result = getQueryParams({
+ registry,
+ search,
+ defaultSearchOperator,
+ });
+ expectResult(
+ result,
+ expect.objectContaining({ default_operator: defaultSearchOperator })
+ );
+ });
+ });
+ });
+
+ describe('when using prefix search (query.bool.should)', () => {
+ const searchQuery = 'foo*';
+
+ const getQueryParamForSearch = ({
+ search,
+ searchFields,
+ type,
+ }: {
+ search?: string;
+ searchFields?: string[];
+ type?: string[];
+ }) =>
+ getQueryParams({
registry,
search,
- searchFields: undefined,
- rootSearchFields: undefined,
+ searchFields,
+ type,
});
- expectResult(result, expect.objectContaining({ lenient: true, fields: ['*'] }));
- });
- it('includes specified search fields for appropriate type/s', () => {
- test({ searchFields: ['title'] });
- });
+ it('uses a `should` clause instead of `must`', () => {
+ const result = getQueryParamForSearch({ search: searchQuery, searchFields: ['title'] });
- it('supports boosting', () => {
- test({ searchFields: ['title^3'] });
+ expect(result.query.bool.must).toBeUndefined();
+ expect(result.query.bool.should).toEqual(expect.any(Array));
+ expect(result.query.bool.should.length).toBeGreaterThanOrEqual(1);
+ expect(result.query.bool.minimum_should_match).toBe(1);
});
-
- it('supports multiple search fields', () => {
- test({ searchFields: ['title, title.raw'] });
+ it('includes the `simple_query_string` in the `should` clauses', () => {
+ const result = getQueryParamForSearch({ search: searchQuery, searchFields: ['title'] });
+ expect(result.query.bool.should[0]).toEqual({
+ simple_query_string: expect.objectContaining({
+ query: searchQuery,
+ }),
+ });
});
- it('includes specified raw search fields', () => {
- test({ rootSearchFields: ['_id'] });
+ it('adds a should clause for each `searchFields` / `type` tuple', () => {
+ const result = getQueryParamForSearch({
+ search: searchQuery,
+ searchFields: ['title', 'desc'],
+ type: ['saved', 'pending'],
+ });
+ const shouldClauses = result.query.bool.should;
+
+ expect(shouldClauses.length).toBe(5);
+
+ const mppClauses = shouldClauses.slice(1);
+
+ expect(
+ mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0])
+ ).toEqual(['saved.title', 'pending.title', 'saved.desc', 'pending.desc']);
});
- it('supports multiple raw search fields', () => {
- test({ rootSearchFields: ['_id', 'originId'] });
+ it('uses all registered types when `type` is not provided', () => {
+ const result = getQueryParamForSearch({
+ search: searchQuery,
+ searchFields: ['title'],
+ type: undefined,
+ });
+ const shouldClauses = result.query.bool.should;
+
+ expect(shouldClauses.length).toBe(5);
+
+ const mppClauses = shouldClauses.slice(1);
+
+ expect(
+ mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0])
+ ).toEqual(['pending.title', 'saved.title', 'shared.title', 'global.title']);
});
- it('supports search fields and raw search fields', () => {
- test({ searchFields: ['title'], rootSearchFields: ['_id'] });
+ it('removes the prefix search wildcard from the query', () => {
+ const result = getQueryParamForSearch({
+ search: searchQuery,
+ searchFields: ['title'],
+ type: ['saved'],
+ });
+ const shouldClauses = result.query.bool.should;
+ const mppClauses = shouldClauses.slice(1);
+
+ expect(mppClauses[0].match_phrase_prefix['saved.title'].query).toEqual('foo');
});
- });
- describe('`defaultSearchOperator` parameter', () => {
- it('does not include default_operator when `defaultSearchOperator` is not specified', () => {
- const result = getQueryParams({
- mappings,
- registry,
- search,
- defaultSearchOperator: undefined,
+ it("defaults to the type's default search field when `searchFields` is not specified", () => {
+ const result = getQueryParamForSearch({
+ search: searchQuery,
+ searchFields: undefined,
+ type: ['saved', 'global'],
});
- expectResult(result, expect.not.objectContaining({ default_operator: expect.anything() }));
+ const shouldClauses = result.query.bool.should;
+
+ expect(shouldClauses.length).toBe(3);
+
+ const mppClauses = shouldClauses.slice(1);
+
+ expect(
+ mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0])
+ ).toEqual(['saved.title', 'global.name']);
});
- it('includes specified default operator', () => {
- const defaultSearchOperator = 'AND';
- const result = getQueryParams({
- mappings,
- registry,
- search,
- defaultSearchOperator,
+ it('supports boosting', () => {
+ const result = getQueryParamForSearch({
+ search: searchQuery,
+ searchFields: ['title^3', 'description'],
+ type: ['saved'],
});
- expectResult(result, expect.objectContaining({ default_operator: defaultSearchOperator }));
+ const shouldClauses = result.query.bool.should;
+
+ expect(shouldClauses.length).toBe(3);
+
+ const mppClauses = shouldClauses.slice(1);
+
+ expect(mppClauses.map((clause: any) => clause.match_phrase_prefix)).toEqual([
+ { 'saved.title': { query: 'foo', boost: 3 } },
+ { 'saved.description': { query: 'foo', boost: 1 } },
+ ]);
});
});
});
@@ -532,7 +682,6 @@ describe('#getQueryParams', () => {
it(`throws for ${type} when namespaces is an empty array`, () => {
expect(() =>
getQueryParams({
- mappings,
registry,
namespaces: [],
})
diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts
index 8d4fe13b9bede..f73777c4f454f 100644
--- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts
+++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts
@@ -20,7 +20,6 @@
import { esKuery } from '../../../es_query';
type KueryNode = any;
-import { getRootPropertiesObjects, IndexMapping } from '../../../mappings';
import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry';
import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils';
@@ -28,22 +27,17 @@ import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils';
* Gets the types based on the type. Uses mappings to support
* null type (all types), a single type string or an array
*/
-function getTypes(mappings: IndexMapping, type?: string | string[]) {
+function getTypes(registry: ISavedObjectTypeRegistry, type?: string | string[]) {
if (!type) {
- return Object.keys(getRootPropertiesObjects(mappings));
+ return registry.getAllTypes().map((registeredType) => registeredType.name);
}
-
- if (Array.isArray(type)) {
- return type;
- }
-
- return [type];
+ return Array.isArray(type) ? type : [type];
}
/**
* Get the field params based on the types, searchFields, and rootSearchFields
*/
-function getFieldsForTypes(
+function getSimpleQueryStringTypeFields(
types: string[],
searchFields: string[] = [],
rootSearchFields: string[] = []
@@ -130,7 +124,6 @@ export interface HasReferenceQueryParams {
export type SearchOperator = 'AND' | 'OR';
interface QueryParams {
- mappings: IndexMapping;
registry: ISavedObjectTypeRegistry;
namespaces?: string[];
type?: string | string[];
@@ -188,11 +181,26 @@ export function getClauseForReference(reference: HasReferenceQueryParams) {
};
}
+// A de-duplicated set of namespaces makes for a more efficient query.
+//
+// Additionally, we treat the `*` namespace as the `default` namespace.
+// In the Default Distribution, the `*` is automatically expanded to include all available namespaces.
+// However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*`
+// to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`,
+// since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place
+// would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard.
+// We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716
+const normalizeNamespaces = (namespacesToNormalize?: string[]) =>
+ namespacesToNormalize
+ ? Array.from(
+ new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x)))
+ )
+ : undefined;
+
/**
* Get the "query" related keys for the search body
*/
export function getQueryParams({
- mappings,
registry,
namespaces,
type,
@@ -206,7 +214,7 @@ export function getQueryParams({
kueryNode,
}: QueryParams) {
const types = getTypes(
- mappings,
+ registry,
typeToNamespacesMap ? Array.from(typeToNamespacesMap.keys()) : type
);
@@ -214,28 +222,10 @@ export function getQueryParams({
hasReference = [hasReference];
}
- // A de-duplicated set of namespaces makes for a more effecient query.
- //
- // Additonally, we treat the `*` namespace as the `default` namespace.
- // In the Default Distribution, the `*` is automatically expanded to include all available namespaces.
- // However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*`
- // to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`,
- // since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place
- // would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard.
- // We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716
- const normalizeNamespaces = (namespacesToNormalize?: string[]) =>
- namespacesToNormalize
- ? Array.from(
- new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x)))
- )
- : undefined;
-
const bool: any = {
filter: [
...(kueryNode != null ? [esKuery.toElasticsearchQuery(kueryNode)] : []),
- ...(hasReference && hasReference.length
- ? [getReferencesFilter(hasReference, hasReferenceOperator)]
- : []),
+ ...(hasReference?.length ? [getReferencesFilter(hasReference, hasReferenceOperator)] : []),
{
bool: {
should: types.map((shouldType) => {
@@ -251,16 +241,133 @@ export function getQueryParams({
};
if (search) {
- bool.must = [
- {
- simple_query_string: {
- query: search,
- ...getFieldsForTypes(types, searchFields, rootSearchFields),
- ...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}),
- },
- },
- ];
+ const useMatchPhrasePrefix = shouldUseMatchPhrasePrefix(search);
+ const simpleQueryStringClause = getSimpleQueryStringClause({
+ search,
+ types,
+ searchFields,
+ rootSearchFields,
+ defaultSearchOperator,
+ });
+
+ if (useMatchPhrasePrefix) {
+ bool.should = [
+ simpleQueryStringClause,
+ ...getMatchPhrasePrefixClauses({ search, searchFields, types, registry }),
+ ];
+ bool.minimum_should_match = 1;
+ } else {
+ bool.must = [simpleQueryStringClause];
+ }
}
return { query: { bool } };
}
+
+// we only want to add match_phrase_prefix clauses
+// if the search is a prefix search
+const shouldUseMatchPhrasePrefix = (search: string): boolean => {
+ return search.trim().endsWith('*');
+};
+
+const getMatchPhrasePrefixClauses = ({
+ search,
+ searchFields,
+ registry,
+ types,
+}: {
+ search: string;
+ searchFields?: string[];
+ types: string[];
+ registry: ISavedObjectTypeRegistry;
+}) => {
+ // need to remove the prefix search operator
+ const query = search.replace(/[*]$/, '');
+ const mppFields = getMatchPhrasePrefixFields({ searchFields, types, registry });
+ return mppFields.map(({ field, boost }) => {
+ return {
+ match_phrase_prefix: {
+ [field]: {
+ query,
+ boost,
+ },
+ },
+ };
+ });
+};
+
+interface FieldWithBoost {
+ field: string;
+ boost?: number;
+}
+
+const getMatchPhrasePrefixFields = ({
+ searchFields = [],
+ types,
+ registry,
+}: {
+ searchFields?: string[];
+ types: string[];
+ registry: ISavedObjectTypeRegistry;
+}): FieldWithBoost[] => {
+ const output: FieldWithBoost[] = [];
+
+ searchFields = searchFields.filter((field) => field !== '*');
+ let fields: string[];
+ if (searchFields.length === 0) {
+ fields = types.reduce((typeFields, type) => {
+ const defaultSearchField = registry.getType(type)?.management?.defaultSearchField;
+ if (defaultSearchField) {
+ return [...typeFields, `${type}.${defaultSearchField}`];
+ }
+ return typeFields;
+ }, [] as string[]);
+ } else {
+ fields = [];
+ for (const field of searchFields) {
+ fields = fields.concat(types.map((type) => `${type}.${field}`));
+ }
+ }
+
+ fields.forEach((rawField) => {
+ const [field, rawBoost] = rawField.split('^');
+ let boost: number = 1;
+ if (rawBoost) {
+ try {
+ boost = parseInt(rawBoost, 10);
+ } catch (e) {
+ boost = 1;
+ }
+ }
+ if (isNaN(boost)) {
+ boost = 1;
+ }
+ output.push({
+ field,
+ boost,
+ });
+ });
+ return output;
+};
+
+const getSimpleQueryStringClause = ({
+ search,
+ types,
+ searchFields,
+ rootSearchFields,
+ defaultSearchOperator,
+}: {
+ search: string;
+ types: string[];
+ searchFields?: string[];
+ rootSearchFields?: string[];
+ defaultSearchOperator?: SearchOperator;
+}) => {
+ return {
+ simple_query_string: {
+ query: search,
+ ...getSimpleQueryStringTypeFields(types, searchFields, rootSearchFields),
+ ...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}),
+ },
+ };
+};
diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts
index a9f26f71a3f2b..3522ab9ef1736 100644
--- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts
+++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts
@@ -76,7 +76,6 @@ describe('getSearchDsl', () => {
getSearchDsl(mappings, registry, opts);
expect(getQueryParams).toHaveBeenCalledTimes(1);
expect(getQueryParams).toHaveBeenCalledWith({
- mappings,
registry,
namespaces: opts.namespaces,
type: opts.type,
diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts
index d5da82e5617be..bddecc4d7f649 100644
--- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts
+++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts
@@ -71,7 +71,6 @@ export function getSearchDsl(
return {
...getQueryParams({
- mappings,
registry,
namespaces,
type,
diff --git a/src/dev/build/tasks/bin/scripts/kibana b/src/dev/build/tasks/bin/scripts/kibana
index c606436c7b83f..a4fc5385500b5 100755
--- a/src/dev/build/tasks/bin/scripts/kibana
+++ b/src/dev/build/tasks/bin/scripts/kibana
@@ -14,7 +14,7 @@ while [ -h "$SCRIPT" ] ; do
done
DIR="$(dirname "${SCRIPT}")/.."
-CONFIG_DIR=${KIBANA_PATH_CONF:-"$DIR/config"}
+CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"}
NODE="${DIR}/node/bin/node"
test -x "$NODE"
if [ ! -x "$NODE" ]; then
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 19487efe1366c..8679cce9b11fc 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/run.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts
@@ -40,7 +40,7 @@ export async function runDockerGenerator(
ubi: boolean = false
) {
// UBI var config
- const baseOSImage = ubi ? 'docker.elastic.co/ubi8/ubi-minimal:latest' : 'centos:8';
+ const baseOSImage = ubi ? 'docker.elastic.co/ubi8/ubi-minimal:8.2' : 'centos:8';
const ubiVersionTag = 'ubi8';
const ubiImageFlavor = ubi ? `-${ubiVersionTag}` : '';
diff --git a/src/plugins/apm_oss/README.asciidoc b/src/plugins/apm_oss/README.asciidoc
new file mode 100644
index 0000000000000..c3c060a99ee27
--- /dev/null
+++ b/src/plugins/apm_oss/README.asciidoc
@@ -0,0 +1,5 @@
+# APM OSS plugin
+
+OSS plugin for APM. Includes index configuration and tutorial resources.
+
+See <<../../x-pack/plugins/apm/readme.md,the X-Pack APM plugin README>> for information about the main APM plugin.
diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx
index 3f7d05e8692c2..feb30b248c066 100644
--- a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx
+++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx
@@ -35,7 +35,7 @@ import { coreMock } from '../../../../../core/public/mocks';
import { CoreStart } from 'kibana/public';
import { AddToLibraryAction } from '.';
import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
-import { ViewMode } from '../../../../embeddable/public';
+import { ErrorEmbeddable, ViewMode } from '../../../../embeddable/public';
const { setup, doStart } = embeddablePluginMock.createInstance();
setup.registerEmbeddableFactory(
@@ -86,6 +86,16 @@ beforeEach(async () => {
}
});
+test('Add to library is incompatible with Error Embeddables', async () => {
+ const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts });
+ const errorEmbeddable = new ErrorEmbeddable(
+ 'Wow what an awful error',
+ { id: ' 404' },
+ embeddable.getRoot() as IContainer
+ );
+ expect(await action.isCompatible({ embeddable: errorEmbeddable })).toBe(false);
+});
+
test('Add to library is compatible when embeddable on dashboard has value type input', async () => {
const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts });
embeddable.updateInput(await embeddable.getInputAsValueType());
@@ -124,19 +134,15 @@ test('Add to library is not compatible when embeddable is not in a dashboard con
expect(await action.isCompatible({ embeddable: orphanContactCard })).toBe(false);
});
-test('Add to library replaces embeddableId but retains panel count', async () => {
+test('Add to library replaces embeddableId and 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 AddToLibraryAction({ toasts: coreStart.notifications.toasts });
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(Object.keys(container.getInput().panels)).toContain(embeddable.id);
+ const newPanel = container.getInput().panels[embeddable.id!];
expect(newPanel.type).toEqual(embeddable.type);
});
@@ -152,15 +158,10 @@ test('Add to library returns reference type input', async () => {
mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: embeddable.id },
mockedByValueInput: { attributes: complicatedAttributes, id: embeddable.id } as EmbeddableInput,
});
- const dashboard = embeddable.getRoot() as IContainer;
- const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels));
const action = new AddToLibraryAction({ toasts: coreStart.notifications.toasts });
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(Object.keys(container.getInput().panels)).toContain(embeddable.id);
+ const newPanel = container.getInput().panels[embeddable.id!];
expect(newPanel.type).toEqual(embeddable.type);
expect(newPanel.explicitInput.attributes).toBeUndefined();
expect(newPanel.explicitInput.savedObjectId).toBe('testSavedObjectId');
diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx
index d89c38f297e8f..179e5d522a2b3 100644
--- a/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx
+++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx
@@ -26,6 +26,7 @@ import {
PanelNotFoundError,
EmbeddableInput,
isReferenceOrValueEmbeddable,
+ isErrorEmbeddable,
} from '../../../../embeddable/public';
import { NotificationsStart } from '../../../../../core/public';
import { DashboardPanelState, DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '..';
@@ -61,7 +62,8 @@ export class AddToLibraryAction implements ActionByType {
}
});
+test('Clone is incompatible with Error Embeddables', async () => {
+ const action = new ClonePanelAction(coreStart);
+ const errorEmbeddable = new ErrorEmbeddable(
+ 'Wow what an awful error',
+ { id: ' 404' },
+ embeddable.getRoot() as IContainer
+ );
+ expect(await action.isCompatible({ embeddable: errorEmbeddable })).toBe(false);
+});
+
test('Clone adds a new embeddable', async () => {
const dashboard = embeddable.getRoot() as IContainer;
const originalPanelCount = Object.keys(dashboard.getInput().panels).length;
@@ -98,7 +108,12 @@ test('Clone adds a new embeddable', async () => {
);
expect(newPanelId).toBeDefined();
const newPanel = container.getInput().panels[newPanelId!];
- expect(newPanel.type).toEqual(embeddable.type);
+ expect(newPanel.type).toEqual('placeholder');
+ // let the placeholder load
+ await dashboard.untilEmbeddableLoaded(newPanelId!);
+ // now wait for the full embeddable to replace it
+ const loadedPanel = await dashboard.untilEmbeddableLoaded(newPanelId!);
+ expect(loadedPanel.type).toEqual(embeddable.type);
});
test('Clones an embeddable without a saved object ID', async () => {
diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx
index dc5887ee0e644..2d98d419689c1 100644
--- a/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx
+++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx
@@ -28,6 +28,7 @@ import {
PanelNotFoundError,
EmbeddableInput,
SavedObjectEmbeddableInput,
+ isErrorEmbeddable,
} from '../../../../embeddable/public';
import {
placePanelBeside,
@@ -66,7 +67,8 @@ export class ClonePanelAction implements ActionByType
public async isCompatible({ embeddable }: ClonePanelActionContext) {
return Boolean(
- embeddable.getInput()?.viewMode !== ViewMode.VIEW &&
+ !isErrorEmbeddable(embeddable) &&
+ embeddable.getInput()?.viewMode !== ViewMode.VIEW &&
embeddable.getRoot() &&
embeddable.getRoot().isContainer &&
embeddable.getRoot().type === DASHBOARD_CONTAINER_TYPE
diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx
index 996649677e6c9..f45d64cdc0ab8 100644
--- a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx
+++ b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx
@@ -30,7 +30,7 @@ import { coreMock } from '../../../../../core/public/mocks';
import { CoreStart } from 'kibana/public';
import { LibraryNotificationAction, UnlinkFromLibraryAction } from '.';
import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
-import { ViewMode } from '../../../../embeddable/public';
+import { ErrorEmbeddable, IContainer, ViewMode } from '../../../../embeddable/public';
const { setup, doStart } = embeddablePluginMock.createInstance();
setup.registerEmbeddableFactory(
@@ -87,6 +87,16 @@ beforeEach(async () => {
embeddable.updateInput({ viewMode: ViewMode.EDIT });
});
+test('Notification is incompatible with Error Embeddables', async () => {
+ const action = new LibraryNotificationAction(unlinkAction);
+ const errorEmbeddable = new ErrorEmbeddable(
+ 'Wow what an awful error',
+ { id: ' 404' },
+ embeddable.getRoot() as IContainer
+ );
+ expect(await action.isCompatible({ embeddable: errorEmbeddable })).toBe(false);
+});
+
test('Notification is shown when embeddable on dashboard has reference type input', async () => {
const action = new LibraryNotificationAction(unlinkAction);
embeddable.updateInput(await embeddable.getInputAsRefType());
diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx
index 6a0b71d8250be..d6e75a3bb132b 100644
--- a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx
+++ b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx
@@ -19,7 +19,12 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
-import { IEmbeddable, ViewMode, isReferenceOrValueEmbeddable } from '../../embeddable_plugin';
+import {
+ IEmbeddable,
+ ViewMode,
+ isReferenceOrValueEmbeddable,
+ isErrorEmbeddable,
+} from '../../embeddable_plugin';
import { ActionByType, IncompatibleActionError } from '../../ui_actions_plugin';
import { reactToUiComponent } from '../../../../kibana_react/public';
import { UnlinkFromLibraryAction } from '.';
@@ -79,6 +84,7 @@ export class LibraryNotificationAction implements ActionByType {
return (
+ !isErrorEmbeddable(embeddable) &&
embeddable.getRoot().isContainer &&
embeddable.getInput()?.viewMode !== ViewMode.VIEW &&
isReferenceOrValueEmbeddable(embeddable) &&
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
index 0f61a74cd7036..f191be6f7baad 100644
--- 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
@@ -30,7 +30,11 @@ import { coreMock } from '../../../../../core/public/mocks';
import { CoreStart } from 'kibana/public';
import { UnlinkFromLibraryAction } from '.';
import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
-import { ViewMode, SavedObjectEmbeddableInput } from '../../../../embeddable/public';
+import {
+ ViewMode,
+ SavedObjectEmbeddableInput,
+ ErrorEmbeddable,
+} from '../../../../embeddable/public';
const { setup, doStart } = embeddablePluginMock.createInstance();
setup.registerEmbeddableFactory(
@@ -80,6 +84,16 @@ beforeEach(async () => {
embeddable.updateInput({ viewMode: ViewMode.EDIT });
});
+test('Unlink is incompatible with Error Embeddables', async () => {
+ const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts });
+ const errorEmbeddable = new ErrorEmbeddable(
+ 'Wow what an awful error',
+ { id: ' 404' },
+ embeddable.getRoot() as IContainer
+ );
+ expect(await action.isCompatible({ embeddable: errorEmbeddable })).toBe(false);
+});
+
test('Unlink is compatible when embeddable on dashboard has reference type input', async () => {
const action = new UnlinkFromLibraryAction({ toasts: coreStart.notifications.toasts });
embeddable.updateInput(await embeddable.getInputAsRefType());
@@ -118,19 +132,14 @@ test('Unlink is not compatible when embeddable is not in a dashboard container',
expect(await action.isCompatible({ embeddable: orphanContactCard })).toBe(false);
});
-test('Unlink replaces embeddableId but retains panel count', async () => {
+test('Unlink replaces embeddableId and 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({ toasts: coreStart.notifications.toasts });
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(Object.keys(container.getInput().panels)).toContain(embeddable.id);
+ const newPanel = container.getInput().panels[embeddable.id!];
expect(newPanel.type).toEqual(embeddable.type);
});
@@ -150,15 +159,10 @@ test('Unlink unwraps all attributes from savedObject', async () => {
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({ toasts: coreStart.notifications.toasts });
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(Object.keys(container.getInput().panels)).toContain(embeddable.id);
+ const newPanel = container.getInput().panels[embeddable.id!];
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
index f5cf8b4e866a8..5e16145364712 100644
--- a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx
+++ b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.tsx
@@ -26,6 +26,7 @@ import {
PanelNotFoundError,
EmbeddableInput,
isReferenceOrValueEmbeddable,
+ isErrorEmbeddable,
} from '../../../../embeddable/public';
import { NotificationsStart } from '../../../../../core/public';
import { DashboardPanelState, DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '..';
@@ -61,7 +62,8 @@ export class UnlinkFromLibraryAction implements ActionByType
merge(
...newChildIds.map((childId) =>
- dashboardContainer!.getChild(childId).getOutput$()
+ dashboardContainer!
+ .getChild(childId)
+ .getOutput$()
+ .pipe(catchError(() => EMPTY))
)
)
)
diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx
index 89aacf2a84029..caa8321d7b8b2 100644
--- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx
@@ -27,6 +27,7 @@ import {
ContactCardEmbeddableInput,
ContactCardEmbeddable,
ContactCardEmbeddableOutput,
+ EMPTY_EMBEDDABLE,
} from '../../embeddable_plugin_test_samples';
import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
@@ -100,6 +101,48 @@ test('DashboardContainer.addNewEmbeddable', async () => {
expect(embeddableInContainer.id).toBe(embeddable.id);
});
+test('DashboardContainer.replacePanel', async (done) => {
+ const ID = '123';
+ const initialInput = getSampleDashboardInput({
+ panels: {
+ [ID]: getSampleDashboardPanel({
+ explicitInput: { firstName: 'Sam', id: ID },
+ type: CONTACT_CARD_EMBEDDABLE,
+ }),
+ },
+ });
+
+ const container = new DashboardContainer(initialInput, options);
+ let counter = 0;
+
+ const subscriptionHandler = jest.fn(({ panels }) => {
+ counter++;
+ expect(panels[ID]).toBeDefined();
+ // It should be called exactly 2 times and exit the second time
+ switch (counter) {
+ case 1:
+ return expect(panels[ID].type).toBe(CONTACT_CARD_EMBEDDABLE);
+
+ case 2: {
+ expect(panels[ID].type).toBe(EMPTY_EMBEDDABLE);
+ subscription.unsubscribe();
+ done();
+ }
+
+ default:
+ throw Error('Called too many times!');
+ }
+ });
+
+ const subscription = container.getInput$().subscribe(subscriptionHandler);
+
+ // replace the panel now
+ container.replacePanel(container.getInput().panels[ID], {
+ type: EMPTY_EMBEDDABLE,
+ explicitInput: { id: ID },
+ });
+});
+
test('Container view mode change propagates to existing children', async () => {
const initialInput = getSampleDashboardInput({
panels: {
diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
index 757488185fe8e..051a7ef8bfb92 100644
--- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx
@@ -154,42 +154,43 @@ export class DashboardContainer extends Container) =>
- this.replacePanel(placeholderPanelState, newPanelState)
- );
+
+ // wait until the placeholder is ready, then replace it with new panel
+ // this is useful as sometimes panels can load faster than the placeholder one (i.e. by value embeddables)
+ this.untilEmbeddableLoaded(originalPanelState.explicitInput.id)
+ .then(() => newStateComplete)
+ .then((newPanelState: Partial) =>
+ this.replacePanel(placeholderPanelState, newPanelState)
+ );
}
public replacePanel(
previousPanelState: DashboardPanelState,
newPanelState: Partial
) {
- // TODO: In the current infrastructure, embeddables in a container do not react properly to
- // changes. Removing the existing embeddable, and adding a new one is a temporary workaround
- // until the container logic is fixed.
-
- const finalPanels = { ...this.input.panels };
- delete finalPanels[previousPanelState.explicitInput.id];
- const newPanelId = newPanelState.explicitInput?.id ? newPanelState.explicitInput.id : uuid.v4();
- finalPanels[newPanelId] = {
- ...previousPanelState,
- ...newPanelState,
- gridData: {
- ...previousPanelState.gridData,
- i: newPanelId,
- },
- explicitInput: {
- ...newPanelState.explicitInput,
- id: newPanelId,
+ // Because the embeddable type can change, we have to operate at the container level here
+ return this.updateInput({
+ panels: {
+ ...this.input.panels,
+ [previousPanelState.explicitInput.id]: {
+ ...previousPanelState,
+ ...newPanelState,
+ gridData: {
+ ...previousPanelState.gridData,
+ },
+ explicitInput: {
+ ...newPanelState.explicitInput,
+ id: previousPanelState.explicitInput.id,
+ },
+ },
},
- };
- this.updateInput({
- panels: finalPanels,
lastReloadRequestTime: new Date().getTime(),
});
}
@@ -201,16 +202,15 @@ export class DashboardContainer extends Container(type: string, explicitInput: Partial, embeddableId?: string) {
const idToReplace = embeddableId || explicitInput.id;
if (idToReplace && this.input.panels[idToReplace]) {
- this.replacePanel(this.input.panels[idToReplace], {
+ return this.replacePanel(this.input.panels[idToReplace], {
type,
explicitInput: {
...explicitInput,
- id: uuid.v4(),
+ id: idToReplace,
},
});
- } else {
- this.addNewEmbeddable(type, explicitInput);
}
+ return this.addNewEmbeddable(type, explicitInput);
}
public render(dom: HTMLElement) {
diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx
index d4d8fd0a4374b..03c92d91a80cc 100644
--- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx
@@ -265,6 +265,7 @@ class DashboardGridUi extends React.Component {
{
@@ -272,6 +273,8 @@ class DashboardGridUi extends React.Component
{
}}
>
{
});
});
+ describe('getFormatterForField', () => {
+ test('should return the default one for empty objects', () => {
+ indexPattern.setFieldFormat('scriptedFieldWithEmptyFormatter', {});
+ expect(
+ indexPattern.getFormatterForField({
+ name: 'scriptedFieldWithEmptyFormatter',
+ type: 'number',
+ esTypes: ['long'],
+ })
+ ).toEqual(
+ expect.objectContaining({
+ convert: expect.any(Function),
+ getConverterFor: expect.any(Function),
+ })
+ );
+ });
+ });
+
describe('toSpec', () => {
test('should match snapshot', () => {
const formatter = {
diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts
index a0f27078543a9..4508d7b1d9082 100644
--- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts
+++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts
@@ -291,15 +291,15 @@ export class IndexPattern implements IIndexPattern {
getFormatterForField(
field: IndexPatternField | IndexPatternField['spec'] | IFieldType
): FieldFormat {
- const formatSpec = this.fieldFormatMap[field.name];
- if (formatSpec) {
- return this.fieldFormats.getInstance(formatSpec.id, formatSpec.params);
- } else {
- return this.fieldFormats.getDefaultInstance(
- field.type as KBN_FIELD_TYPES,
- field.esTypes as ES_FIELD_TYPES[]
- );
+ const fieldFormat = this.getFormatterForFieldNoDefault(field.name);
+ if (fieldFormat) {
+ return fieldFormat;
}
+
+ return this.fieldFormats.getDefaultInstance(
+ field.type as KBN_FIELD_TYPES,
+ field.esTypes as ES_FIELD_TYPES[]
+ );
}
/**
@@ -308,7 +308,7 @@ export class IndexPattern implements IIndexPattern {
*/
getFormatterForFieldNoDefault(fieldname: string) {
const formatSpec = this.fieldFormatMap[fieldname];
- if (formatSpec) {
+ if (formatSpec?.id) {
return this.fieldFormats.getInstance(formatSpec.id, formatSpec.params);
}
}
diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts
index 09a13762d4d70..897b60e91b100 100644
--- a/src/plugins/data/common/search/aggs/types.ts
+++ b/src/plugins/data/common/search/aggs/types.ts
@@ -94,7 +94,7 @@ export interface AggsCommonStart {
*/
getDateMetaByDatatableColumn: (
column: DatatableColumn
- ) => Promise;
+ ) => Promise;
createAggConfigs: (
indexPattern: IndexPattern,
configStates?: CreateAggConfigParams[],
diff --git a/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts b/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts
index e56d622734554..8eb076f5b7906 100644
--- a/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts
+++ b/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts
@@ -91,6 +91,43 @@ describe('getDateMetaByDatatableColumn', () => {
});
});
+ it('throws if unable to resolve interval', async () => {
+ await expect(
+ getDateMetaByDatatableColumn(params)({
+ id: 'test',
+ name: 'test',
+ meta: {
+ type: 'date',
+ source: 'esaggs',
+ sourceParams: {
+ type: BUCKET_TYPES.DATE_HISTOGRAM,
+ params: {
+ time_zone: 'UTC',
+ interval: 'auto',
+ },
+ },
+ },
+ })
+ ).rejects.toBeDefined();
+
+ await expect(
+ getDateMetaByDatatableColumn(params)({
+ id: 'test',
+ name: 'test',
+ meta: {
+ type: 'date',
+ source: 'esaggs',
+ sourceParams: {
+ type: BUCKET_TYPES.DATE_HISTOGRAM,
+ params: {
+ time_zone: 'UTC',
+ },
+ },
+ },
+ })
+ ).rejects.toBeDefined();
+ });
+
it('returns resolved auto interval', async () => {
expect(
await getDateMetaByDatatableColumn(params)({
@@ -106,8 +143,8 @@ describe('getDateMetaByDatatableColumn', () => {
interval: 'auto',
},
appliedTimeRange: {
- from: 'now-5d',
- to: 'now',
+ from: '2020-10-05T00:00:00.000Z',
+ to: '2020-10-10T00:00:00.000Z',
},
},
},
diff --git a/src/plugins/data/common/search/aggs/utils/time_column_meta.ts b/src/plugins/data/common/search/aggs/utils/time_column_meta.ts
index 1bea716c6a049..7ed8cb79f63f4 100644
--- a/src/plugins/data/common/search/aggs/utils/time_column_meta.ts
+++ b/src/plugins/data/common/search/aggs/utils/time_column_meta.ts
@@ -38,11 +38,11 @@ export const getDateMetaByDatatableColumn = ({
getConfig,
}: DateMetaByColumnDeps) => async (
column: DatatableColumn
-): Promise => {
+): Promise => {
if (column.meta.source !== 'esaggs') return;
if (column.meta.sourceParams?.type !== BUCKET_TYPES.DATE_HISTOGRAM) return;
const params = column.meta.sourceParams.params as AggParamsDateHistogram;
- const appliedTimeRange = column.meta.sourceParams.appliedTimeRange as TimeRange;
+ const appliedTimeRange = column.meta.sourceParams.appliedTimeRange as TimeRange | undefined;
const tz = inferTimeZone(
params,
@@ -52,9 +52,11 @@ export const getDateMetaByDatatableColumn = ({
);
const interval =
- params.interval === 'auto' ? calculateAutoTimeExpression(appliedTimeRange) : params.interval;
+ params.interval === 'auto' && appliedTimeRange
+ ? calculateAutoTimeExpression(appliedTimeRange)
+ : params.interval;
- if (!interval) {
+ if (!interval || interval === 'auto') {
throw new Error('time interval could not be determined');
}
diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts
index dba77d398c8b6..3932484801fa8 100644
--- a/src/plugins/data/public/search/expressions/esaggs.ts
+++ b/src/plugins/data/public/search/expressions/esaggs.ts
@@ -267,6 +267,8 @@ export const esaggs = (): EsaggsExpressionFunctionDefinition => ({
searchSource.setField('index', indexPattern);
searchSource.setField('size', 0);
+ const resolvedTimeRange = input?.timeRange && calculateBounds(input.timeRange);
+
const response = await handleCourierRequest({
searchSource,
aggs,
@@ -303,7 +305,10 @@ export const esaggs = (): EsaggsExpressionFunctionDefinition => ({
input?.timeRange &&
args.timeFields &&
args.timeFields.includes(column.aggConfig.params.field?.name)
- ? { from: input.timeRange.from, to: input.timeRange.to }
+ ? {
+ from: resolvedTimeRange?.min?.toISOString(),
+ to: resolvedTimeRange?.max?.toISOString(),
+ }
: undefined,
...column.aggConfig.serialize(),
},
diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts
index 9f701f021162a..4dede8bf5d752 100644
--- a/src/plugins/embeddable/public/lib/containers/container.ts
+++ b/src/plugins/embeddable/public/lib/containers/container.ts
@@ -19,6 +19,7 @@
import uuid from 'uuid';
import { merge, Subscription } from 'rxjs';
+import { startWith, pairwise } from 'rxjs/operators';
import {
Embeddable,
EmbeddableInput,
@@ -55,7 +56,12 @@ export abstract class Container<
parent?: Container
) {
super(input, output, parent);
- this.subscription = this.getInput$().subscribe(() => this.maybeUpdateChildren());
+ this.subscription = this.getInput$()
+ // At each update event, get both the previous and current state
+ .pipe(startWith(input), pairwise())
+ .subscribe(([{ panels: prevPanels }, { panels: currentPanels }]) => {
+ this.maybeUpdateChildren(currentPanels, prevPanels);
+ });
}
public updateInputForChild(
@@ -329,16 +335,30 @@ export abstract class Container<
return embeddable;
}
- private maybeUpdateChildren() {
- const allIds = Object.keys({ ...this.input.panels, ...this.output.embeddableLoaded });
+ private panelHasChanged(currentPanel: PanelState, prevPanel: PanelState) {
+ if (currentPanel.type !== prevPanel.type) {
+ return true;
+ }
+ }
+
+ private maybeUpdateChildren(
+ currentPanels: TContainerInput['panels'],
+ prevPanels: TContainerInput['panels']
+ ) {
+ const allIds = Object.keys({ ...currentPanels, ...this.output.embeddableLoaded });
allIds.forEach((id) => {
- if (this.input.panels[id] !== undefined && this.output.embeddableLoaded[id] === undefined) {
- this.onPanelAdded(this.input.panels[id]);
- } else if (
- this.input.panels[id] === undefined &&
- this.output.embeddableLoaded[id] !== undefined
- ) {
- this.onPanelRemoved(id);
+ if (currentPanels[id] !== undefined && this.output.embeddableLoaded[id] === undefined) {
+ return this.onPanelAdded(currentPanels[id]);
+ }
+ if (currentPanels[id] === undefined && this.output.embeddableLoaded[id] !== undefined) {
+ return this.onPanelRemoved(id);
+ }
+ // In case of type change, remove and add a panel with the same id
+ if (currentPanels[id] && prevPanels[id]) {
+ if (this.panelHasChanged(currentPanels[id], prevPanels[id])) {
+ this.onPanelRemoved(id);
+ this.onPanelAdded(currentPanels[id]);
+ }
}
});
}
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
index c7afc157c1452..31df5c5085f8b 100644
--- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
@@ -43,6 +43,7 @@ export abstract class Embeddable<
public readonly isContainer: boolean = false;
public abstract readonly type: string;
public readonly id: string;
+ public fatalError?: Error;
protected output: TEmbeddableOutput;
protected input: TEmbeddableInput;
@@ -88,9 +89,12 @@ export abstract class Embeddable<
map(({ title }) => title || ''),
distinctUntilChanged()
)
- .subscribe((title) => {
- this.renderComplete.setTitle(title);
- });
+ .subscribe(
+ (title) => {
+ this.renderComplete.setTitle(title);
+ },
+ () => {}
+ );
}
public getIsContainer(): this is IContainer {
@@ -193,6 +197,11 @@ export abstract class Embeddable<
}
}
+ protected onFatalError(e: Error) {
+ this.fatalError = e;
+ this.output$.error(e);
+ }
+
private onResetInput(newInput: TEmbeddableInput) {
if (!isEqual(this.input, newInput)) {
const oldLastReloadRequestTime = this.input.lastReloadRequestTime;
diff --git a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx
index cdbe7af98a4f4..34d971cbb717a 100644
--- a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx
+++ b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx
@@ -30,7 +30,7 @@ export const ERROR_EMBEDDABLE_TYPE = 'error';
export function isErrorEmbeddable(
embeddable: TEmbeddable | ErrorEmbeddable
): embeddable is ErrorEmbeddable {
- return (embeddable as ErrorEmbeddable).error !== undefined;
+ return Boolean(embeddable.fatalError || (embeddable as ErrorEmbeddable).error !== undefined);
}
export class ErrorEmbeddable extends Embeddable {
diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
index 3843950c164c9..5a73df2e13861 100644
--- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
+++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
@@ -85,6 +85,11 @@ export interface IEmbeddable<
*/
enhancements?: object;
+ /**
+ * If this embeddable has encountered a fatal error, that error will be stored here
+ **/
+ fatalError?: Error;
+
/**
* A functional representation of the isContainer variable, but helpful for typescript to
* know the shape if this returns true
diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
index 1cd48e85469fd..4a1fd22894e7e 100644
--- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
+++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
@@ -50,7 +50,7 @@ import { EditPanelAction } from '../actions';
import { CustomizePanelModal } from './panel_header/panel_actions/customize_title/customize_panel_modal';
import { EmbeddableStart } from '../../plugin';
import { EmbeddableErrorLabel } from './embeddable_error_label';
-import { EmbeddableStateTransfer } from '..';
+import { EmbeddableStateTransfer, ErrorEmbeddable } from '..';
const sortByOrderField = (
{ order: orderA }: { order?: number },
@@ -85,6 +85,7 @@ interface State {
notifications: Array>;
loading?: boolean;
error?: EmbeddableError;
+ errorEmbeddable?: ErrorEmbeddable;
}
interface PanelUniversalActions {
@@ -199,6 +200,9 @@ export class EmbeddablePanel extends React.Component {
if (this.parentSubscription) {
this.parentSubscription.unsubscribe();
}
+ if (this.state.errorEmbeddable) {
+ this.state.errorEmbeddable.destroy();
+ }
this.props.embeddable.destroy();
}
@@ -257,12 +261,21 @@ export class EmbeddablePanel extends React.Component {
public componentDidMount() {
if (this.embeddableRoot.current) {
this.subscription.add(
- this.props.embeddable.getOutput$().subscribe((output: EmbeddableOutput) => {
- this.setState({
- error: output.error,
- loading: output.loading,
- });
- })
+ this.props.embeddable.getOutput$().subscribe(
+ (output: EmbeddableOutput) => {
+ this.setState({
+ error: output.error,
+ loading: output.loading,
+ });
+ },
+ (error) => {
+ if (this.embeddableRoot.current) {
+ const errorEmbeddable = new ErrorEmbeddable(error, { id: this.props.embeddable.id });
+ errorEmbeddable.render(this.embeddableRoot.current);
+ this.setState({ errorEmbeddable });
+ }
+ }
+ )
);
this.props.embeddable.render(this.embeddableRoot.current);
}
diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md
index 8f0f56c4e1a16..4406dded98547 100644
--- a/src/plugins/embeddable/public/public.api.md
+++ b/src/plugins/embeddable/public/public.api.md
@@ -278,6 +278,8 @@ export abstract class Embeddable>;
// (undocumented)
getInput(): Readonly;
@@ -298,6 +300,8 @@ export abstract class Embeddable {
destroy(): void;
enhancements?: object;
+ fatalError?: Error;
getInput$(): Readonly>;
getInput(): Readonly;
getInspectorAdapters(): Adapters | undefined;
diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md
index e6bff703aadcd..773d61ebe2e28 100644
--- a/src/plugins/expressions/public/public.api.md
+++ b/src/plugins/expressions/public/public.api.md
@@ -1057,7 +1057,7 @@ export interface Range {
// Warning: (ae-missing-release-tag) "ReactExpressionRenderer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
-export const ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element;
+export const ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, onData$, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element;
// Warning: (ae-missing-release-tag) "ReactExpressionRendererProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -1072,6 +1072,8 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams {
// (undocumented)
expression: string | ExpressionAstExpression;
// (undocumented)
+ onData$?: (data: TData, adapters?: TInspectorAdapters) => void;
+ // (undocumented)
onEvent?: (event: ExpressionRendererEvent) => void;
// (undocumented)
padding?: 'xs' | 's' | 'm' | 'l' | 'xl';
diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx
index 052c2a9f6a24a..e52d4d153882f 100644
--- a/src/plugins/expressions/public/react_expression_renderer.test.tsx
+++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx
@@ -195,6 +195,38 @@ describe('ExpressionRenderer', () => {
expect(instance.find('[data-test-subj="custom-error"]')).toHaveLength(0);
});
+ it('should call onData$ prop on every data$ observable emission in loader', () => {
+ const dataSubject = new Subject();
+ const data$ = dataSubject.asObservable().pipe(share());
+
+ const newData = {};
+ const inspectData = {};
+ const onData$ = jest.fn();
+
+ (ExpressionLoader as jest.Mock).mockImplementation(() => {
+ return {
+ render$: new Subject(),
+ data$,
+ loading$: new Subject(),
+ events$: new Subject(),
+ update: jest.fn(),
+ inspect: jest.fn(() => inspectData),
+ };
+ });
+
+ mount( );
+
+ expect(onData$).toHaveBeenCalledTimes(0);
+
+ act(() => {
+ dataSubject.next(newData);
+ });
+
+ expect(onData$).toHaveBeenCalledTimes(1);
+ expect(onData$.mock.calls[0][0]).toBe(newData);
+ expect(onData$.mock.calls[0][1]).toBe(inspectData);
+ });
+
it('should fire onEvent prop on every events$ observable emission in loader', () => {
const dataSubject = new Subject();
const data$ = dataSubject.asObservable().pipe(share());
diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx
index fecebf36ab7e6..894325c8b65f7 100644
--- a/src/plugins/expressions/public/react_expression_renderer.tsx
+++ b/src/plugins/expressions/public/react_expression_renderer.tsx
@@ -41,6 +41,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams {
) => React.ReactElement | React.ReactElement[];
padding?: 'xs' | 's' | 'm' | 'l' | 'xl';
onEvent?: (event: ExpressionRendererEvent) => void;
+ onData$?: (data: TData, adapters?: TInspectorAdapters) => void;
/**
* An observable which can be used to re-run the expression without destroying the component
*/
@@ -71,6 +72,7 @@ export const ReactExpressionRenderer = ({
renderError,
expression,
onEvent,
+ onData$,
reload$,
debounce,
...expressionLoaderOptions
@@ -135,6 +137,13 @@ export const ReactExpressionRenderer = ({
})
);
}
+ if (onData$) {
+ subs.push(
+ expressionLoaderRef.current.data$.subscribe((newData) => {
+ onData$(newData, expressionLoaderRef.current?.inspect());
+ })
+ );
+ }
subs.push(
expressionLoaderRef.current.loading$.subscribe(() => {
hasHandledErrorRef.current = false;
diff --git a/src/plugins/input_control_vis/public/input_control_vis_type.ts b/src/plugins/input_control_vis/public/input_control_vis_type.ts
index 782df67f5c58a..6e33e18c1603b 100644
--- a/src/plugins/input_control_vis/public/input_control_vis_type.ts
+++ b/src/plugins/input_control_vis/public/input_control_vis_type.ts
@@ -18,8 +18,7 @@
*/
import { i18n } from '@kbn/i18n';
-
-import { BaseVisTypeOptions } from 'src/plugins/visualizations/public';
+import { VisGroups, BaseVisTypeOptions } from '../../visualizations/public';
import { createInputControlVisController } from './vis_controller';
import { getControlsTab } from './components/editor/controls_tab';
import { OptionsTab } from './components/editor/options_tab';
@@ -37,8 +36,9 @@ export function createInputControlVisTypeDefinition(
defaultMessage: 'Controls',
}),
icon: 'controlsHorizontal',
+ group: VisGroups.TOOLS,
description: i18n.translate('inputControl.register.controlsDescription', {
- defaultMessage: 'Create interactive controls for easy dashboard manipulation.',
+ defaultMessage: 'Add dropdown menus and range sliders to your dashboard.',
}),
stage: 'experimental',
visualization: InputControlVisController,
diff --git a/src/plugins/inspector/public/plugin.tsx b/src/plugins/inspector/public/plugin.tsx
index f906dbcab8043..07ef7c8fbab0d 100644
--- a/src/plugins/inspector/public/plugin.tsx
+++ b/src/plugins/inspector/public/plugin.tsx
@@ -70,7 +70,7 @@ export class InspectorPublicPlugin implements Plugin {
public async setup(core: CoreSetup) {
this.views = new InspectorViewRegistry();
- this.views.register(getDataViewDescription(core.uiSettings));
+ this.views.register(getDataViewDescription());
this.views.register(getRequestsViewDescription());
return {
@@ -101,7 +101,14 @@ export class InspectorPublicPlugin implements Plugin {
}
return core.overlays.openFlyout(
- toMountPoint( ),
+ toMountPoint(
+
+ ),
{
'data-test-subj': 'inspectorPanel',
closeButtonAriaLabel: closeButtonLabel,
diff --git a/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap b/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap
index 709c0bfe69f0b..7fb00fe8d40c4 100644
--- a/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap
+++ b/src/plugins/inspector/public/ui/__snapshots__/inspector_panel.test.tsx.snap
@@ -10,6 +10,11 @@ exports[`InspectorPanel should render as expected 1`] = `
},
}
}
+ dependencies={
+ Object {
+ "uiSettings": Object {},
+ }
+ }
intl={
Object {
"defaultFormats": Object {},
@@ -135,216 +140,228 @@ exports[`InspectorPanel should render as expected 1`] = `
]
}
>
-
-
-
-
-
-
-
-
- Inspector
-
-
-
-
-
-
+ Inspector
+
+
+
+
+
-
+
-
-
-
+ }
+ }
+ views={
+ Array [
+ Object {
+ "component": [Function],
+ "order": 200,
+ "title": "View 1",
+ },
+ Object {
+ "component": [Function],
+ "order": 100,
+ "shouldShow": [Function],
+ "title": "Foo View",
+ },
+ Object {
+ "component": [Function],
+ "order": 200,
+ "shouldShow": [Function],
+ "title": "Never",
+ },
+ ]
}
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- id="inspectorViewChooser"
- isOpen={false}
- ownFocus={true}
- panelPaddingSize="none"
- repositionOnScroll={true}
>
-
+
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ id="inspectorViewChooser"
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="none"
+ repositionOnScroll={true}
>
-
-
-
-
-
-
-
-
-
-
+
+
+
- View: View 1
-
+
+ View: View 1
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
- View 1
-
-
+ }
+ >
+
+
+ View 1
+
+
+
+
-
-
+
+
`;
diff --git a/src/plugins/inspector/public/ui/inspector_panel.scss b/src/plugins/inspector/public/ui/inspector_panel.scss
index ff0b491e1222b..2a6cfed66e4ff 100644
--- a/src/plugins/inspector/public/ui/inspector_panel.scss
+++ b/src/plugins/inspector/public/ui/inspector_panel.scss
@@ -1,11 +1,15 @@
.insInspectorPanel__flyoutBody {
- // TODO: EUI to allow for custom classNames to inner elements
- // Or supply this as default
- > div {
+ .euiFlyoutBody__overflowContent {
+ height: 100%;
display: flex;
+ flex-wrap: nowrap;
flex-direction: column;
- > div {
+ >div {
+ flex-grow: 0;
+ }
+
+ .insRequestCodeViewer {
flex-grow: 1;
}
}
diff --git a/src/plugins/inspector/public/ui/inspector_panel.test.tsx b/src/plugins/inspector/public/ui/inspector_panel.test.tsx
index 23f698c23793b..67e197abe7134 100644
--- a/src/plugins/inspector/public/ui/inspector_panel.test.tsx
+++ b/src/plugins/inspector/public/ui/inspector_panel.test.tsx
@@ -22,10 +22,12 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { InspectorPanel } from './inspector_panel';
import { InspectorViewDescription } from '../types';
import { Adapters } from '../../common';
+import type { IUiSettingsClient } from 'kibana/public';
describe('InspectorPanel', () => {
let adapters: Adapters;
let views: InspectorViewDescription[];
+ const uiSettings: IUiSettingsClient = {} as IUiSettingsClient;
beforeEach(() => {
adapters = {
@@ -62,12 +64,16 @@ describe('InspectorPanel', () => {
});
it('should render as expected', () => {
- const component = mountWithIntl( );
+ const component = mountWithIntl(
+
+ );
expect(component).toMatchSnapshot();
});
it('should not allow updating adapters', () => {
- const component = mountWithIntl( );
+ const component = mountWithIntl(
+
+ );
adapters.notAllowed = {};
expect(() => component.setProps({ adapters })).toThrow();
});
diff --git a/src/plugins/inspector/public/ui/inspector_panel.tsx b/src/plugins/inspector/public/ui/inspector_panel.tsx
index 37a51257112d6..dbad202953b0b 100644
--- a/src/plugins/inspector/public/ui/inspector_panel.tsx
+++ b/src/plugins/inspector/public/ui/inspector_panel.tsx
@@ -19,12 +19,21 @@
import './inspector_panel.scss';
import { i18n } from '@kbn/i18n';
-import React, { Component } from 'react';
+import React, { Component, Suspense } from 'react';
import PropTypes from 'prop-types';
-import { EuiFlexGroup, EuiFlexItem, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyoutHeader,
+ EuiTitle,
+ EuiFlyoutBody,
+ EuiLoadingSpinner,
+} from '@elastic/eui';
+import { IUiSettingsClient } from 'kibana/public';
import { InspectorViewDescription } from '../types';
import { Adapters } from '../../common';
import { InspectorViewChooser } from './inspector_view_chooser';
+import { KibanaContextProvider } from '../../../kibana_react/public';
function hasAdaptersChanged(oldAdapters: Adapters, newAdapters: Adapters) {
return (
@@ -41,6 +50,9 @@ interface InspectorPanelProps {
adapters: Adapters;
title?: string;
views: InspectorViewDescription[];
+ dependencies: {
+ uiSettings: IUiSettingsClient;
+ };
}
interface InspectorPanelState {
@@ -95,19 +107,21 @@ export class InspectorPanel extends Component
+ }>
+
+
);
}
render() {
- const { views, title } = this.props;
+ const { views, title, dependencies } = this.props;
const { selectedView } = this.state;
return (
-
+
@@ -127,7 +141,7 @@ export class InspectorPanel extends Component
{this.renderSelectedPanel()}
-
+
);
}
}
diff --git a/src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap
index 2632afff2f63b..3bd3bb6531cc7 100644
--- a/src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap
+++ b/src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Inspector Data View component should render empty state 1`] = `
-
-
+
`;
exports[`Inspector Data View component should render loading state 1`] = `
-
+ loading
+
}
intl={
Object {
@@ -431,204 +439,9 @@ exports[`Inspector Data View component should render loading state 1`] = `
"timeZone": null,
}
}
- title="Test Data"
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Gathering data
-
-
-
-
-
-
-
-
-
-
-
-
+
+ loading
+
+
`;
diff --git a/src/plugins/inspector/public/views/data/components/data_view.test.tsx b/src/plugins/inspector/public/views/data/components/data_view.test.tsx
index bd78bca42c479..6a7f878ef807e 100644
--- a/src/plugins/inspector/public/views/data/components/data_view.test.tsx
+++ b/src/plugins/inspector/public/views/data/components/data_view.test.tsx
@@ -17,11 +17,10 @@
* under the License.
*/
-import React from 'react';
+import React, { Suspense } from 'react';
import { getDataViewDescription } from '../index';
import { DataAdapter } from '../../../../common/adapters/data';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
-import { IUiSettingsClient } from '../../../../../../core/public';
jest.mock('../lib/export_csv', () => ({
exportAsCsv: jest.fn(),
@@ -31,9 +30,7 @@ describe('Inspector Data View', () => {
let DataView: any;
beforeEach(() => {
- const uiSettings = {} as IUiSettingsClient;
-
- DataView = getDataViewDescription(uiSettings);
+ DataView = getDataViewDescription();
});
it('should only show if data adapter is present', () => {
@@ -51,7 +48,12 @@ describe('Inspector Data View', () => {
});
it('should render loading state', () => {
- const component = mountWithIntl( ); // eslint-disable-line react/jsx-pascal-case
+ const DataViewComponent = DataView.component;
+ const component = mountWithIntl(
+ loading}>
+
+
+ );
expect(component).toMatchSnapshot();
});
diff --git a/src/plugins/inspector/public/views/data/components/data_view.tsx b/src/plugins/inspector/public/views/data/components/data_view.tsx
index 1a2b6f9922d2d..100fa7787321c 100644
--- a/src/plugins/inspector/public/views/data/components/data_view.tsx
+++ b/src/plugins/inspector/public/views/data/components/data_view.tsx
@@ -38,6 +38,7 @@ import {
TabularCallback,
} from '../../../../common/adapters/data/types';
import { IUiSettingsClient } from '../../../../../../core/public';
+import { withKibana, KibanaReactContextValue } from '../../../../../kibana_react/public';
interface DataViewComponentState {
tabularData: TabularData | null;
@@ -47,20 +48,23 @@ interface DataViewComponentState {
}
interface DataViewComponentProps extends InspectorViewProps {
- uiSettings: IUiSettingsClient;
+ kibana: KibanaReactContextValue<{ uiSettings: IUiSettingsClient }>;
}
-export class DataViewComponent extends Component {
+class DataViewComponent extends Component {
static propTypes = {
- uiSettings: PropTypes.object.isRequired,
adapters: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
+ kibana: PropTypes.object,
};
state = {} as DataViewComponentState;
_isMounted = false;
- static getDerivedStateFromProps(nextProps: InspectorViewProps, state: DataViewComponentState) {
+ static getDerivedStateFromProps(
+ nextProps: DataViewComponentProps,
+ state: DataViewComponentState
+ ) {
if (state && nextProps.adapters === state.adapters) {
return null;
}
@@ -172,8 +176,12 @@ export class DataViewComponent extends Component
);
}
}
+
+// default export required for React.Lazy
+// eslint-disable-next-line import/no-default-export
+export default withKibana(DataViewComponent);
diff --git a/src/plugins/inspector/public/views/data/index.tsx b/src/plugins/inspector/public/views/data/index.ts
similarity index 72%
rename from src/plugins/inspector/public/views/data/index.tsx
rename to src/plugins/inspector/public/views/data/index.ts
index b02e02bbe6b6b..d201ad89022be 100644
--- a/src/plugins/inspector/public/views/data/index.tsx
+++ b/src/plugins/inspector/public/views/data/index.ts
@@ -16,17 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React from 'react';
+import { lazy } from 'react';
import { i18n } from '@kbn/i18n';
-import { DataViewComponent } from './components/data_view';
-import { InspectorViewDescription, InspectorViewProps } from '../../types';
+import { InspectorViewDescription } from '../../types';
import { Adapters } from '../../../common';
-import { IUiSettingsClient } from '../../../../../core/public';
-export const getDataViewDescription = (
- uiSettings: IUiSettingsClient
-): InspectorViewDescription => ({
+const DataViewComponent = lazy(() => import('./components/data_view'));
+
+export const getDataViewDescription = (): InspectorViewDescription => ({
title: i18n.translate('inspector.data.dataTitle', {
defaultMessage: 'Data',
}),
@@ -37,7 +35,5 @@ export const getDataViewDescription = (
shouldShow(adapters: Adapters) {
return Boolean(adapters.data);
},
- component: (props: InspectorViewProps) => (
-
- ),
+ component: DataViewComponent,
});
diff --git a/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx b/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx
new file mode 100644
index 0000000000000..71499d46071c8
--- /dev/null
+++ b/src/plugins/inspector/public/views/requests/components/details/req_code_viewer.tsx
@@ -0,0 +1,82 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { EuiFlexItem, EuiFlexGroup, EuiCopy, EuiButtonEmpty, EuiSpacer } from '@elastic/eui';
+
+import { CodeEditor } from '../../../../../../kibana_react/public';
+
+interface RequestCodeViewerProps {
+ json: string;
+}
+
+const copyToClipboardLabel = i18n.translate('inspector.requests.copyToClipboardLabel', {
+ defaultMessage: 'Copy to clipboard',
+});
+
+/**
+ * @internal
+ */
+export const RequestCodeViewer = ({ json }: RequestCodeViewerProps) => (
+
+
+
+
+
+ {(copy) => (
+
+ {copyToClipboardLabel}
+
+ )}
+
+
+
+
+ {}}
+ options={{
+ readOnly: true,
+ lineNumbers: 'off',
+ fontSize: 12,
+ minimap: {
+ enabled: false,
+ },
+ scrollBeyondLastLine: false,
+ wordWrap: 'on',
+ wrappingIndent: 'indent',
+ automaticLayout: true,
+ }}
+ />
+
+
+);
diff --git a/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx b/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx
index d7cb8f5745613..47ed226c24a5c 100644
--- a/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx
+++ b/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx
@@ -19,9 +19,9 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { EuiCodeBlock } from '@elastic/eui';
import { Request } from '../../../../../common/adapters/request/types';
import { RequestDetailsProps } from '../types';
+import { RequestCodeViewer } from './req_code_viewer';
export class RequestDetailsRequest extends Component {
static propTypes = {
@@ -37,15 +37,6 @@ export class RequestDetailsRequest extends Component {
return null;
}
- return (
-
- {JSON.stringify(json, null, 2)}
-
- );
+ return ;
}
}
diff --git a/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx b/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx
index 933495ff47396..5ad5cc0537ada 100644
--- a/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx
+++ b/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx
@@ -19,9 +19,9 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { EuiCodeBlock } from '@elastic/eui';
import { Request } from '../../../../../common/adapters/request/types';
import { RequestDetailsProps } from '../types';
+import { RequestCodeViewer } from './req_code_viewer';
export class RequestDetailsResponse extends Component {
static propTypes = {
@@ -40,15 +40,6 @@ export class RequestDetailsResponse extends Component {
return null;
}
- return (
-
- {JSON.stringify(responseJSON, null, 2)}
-
- );
+ return ;
}
}
diff --git a/src/plugins/inspector/public/views/requests/components/requests_view.tsx b/src/plugins/inspector/public/views/requests/components/requests_view.tsx
index 13575de0c5064..7762689daf4e6 100644
--- a/src/plugins/inspector/public/views/requests/components/requests_view.tsx
+++ b/src/plugins/inspector/public/views/requests/components/requests_view.tsx
@@ -175,3 +175,7 @@ export class RequestsViewComponent extends Component import('./components/requests_view'));
+
export const getRequestsViewDescription = (): InspectorViewDescription => ({
title: i18n.translate('inspector.requests.requestsTitle', {
defaultMessage: 'Requests',
diff --git a/src/plugins/legacy_export/README.md b/src/plugins/legacy_export/README.md
new file mode 100644
index 0000000000000..050e39b8f19e4
--- /dev/null
+++ b/src/plugins/legacy_export/README.md
@@ -0,0 +1,3 @@
+# `legacyExport` plugin
+
+The `legacyExport` plugin adds support for the legacy saved objects export format.
diff --git a/src/plugins/lens_oss/README.md b/src/plugins/lens_oss/README.md
new file mode 100644
index 0000000000000..187da2497026e
--- /dev/null
+++ b/src/plugins/lens_oss/README.md
@@ -0,0 +1,6 @@
+# lens_oss
+
+The lens_oss plugin registers the lens visualization on OSS.
+It is registered as disabled. The x-pack plugin should unregister this.
+
+`visualizations.unregisterAlias('lensOss')`
\ No newline at end of file
diff --git a/src/plugins/lens_oss/common/constants.ts b/src/plugins/lens_oss/common/constants.ts
new file mode 100644
index 0000000000000..ac92c9e196993
--- /dev/null
+++ b/src/plugins/lens_oss/common/constants.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 APP_NAME = 'lens';
+export const PLUGIN_ID_OSS = 'lensOss';
+export const APP_PATH = '#/';
+export const APP_ICON = 'lensApp';
diff --git a/src/plugins/visualizations/public/wizard/type_selection/index.ts b/src/plugins/lens_oss/common/index.ts
similarity index 94%
rename from src/plugins/visualizations/public/wizard/type_selection/index.ts
rename to src/plugins/lens_oss/common/index.ts
index c4093b4dec3e8..fd1c2843d6b26 100644
--- a/src/plugins/visualizations/public/wizard/type_selection/index.ts
+++ b/src/plugins/lens_oss/common/index.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export { TypeSelection } from './type_selection';
+export * from './constants';
diff --git a/src/plugins/lens_oss/config.ts b/src/plugins/lens_oss/config.ts
new file mode 100644
index 0000000000000..6749bd83de39f
--- /dev/null
+++ b/src/plugins/lens_oss/config.ts
@@ -0,0 +1,26 @@
+/*
+ * 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 { schema, TypeOf } from '@kbn/config-schema';
+
+export const configSchema = schema.object({
+ enabled: schema.boolean({ defaultValue: true }),
+});
+
+export type ConfigSchema = TypeOf;
diff --git a/src/plugins/lens_oss/kibana.json b/src/plugins/lens_oss/kibana.json
new file mode 100644
index 0000000000000..3e3d3585f37fb
--- /dev/null
+++ b/src/plugins/lens_oss/kibana.json
@@ -0,0 +1,10 @@
+{
+ "id": "lensOss",
+ "version": "kibana",
+ "ui": true,
+ "server": true,
+ "requiredPlugins": [
+ "visualizations"
+ ],
+ "extraPublicDirs": ["common/constants"]
+}
diff --git a/src/plugins/lens_oss/public/index.ts b/src/plugins/lens_oss/public/index.ts
new file mode 100644
index 0000000000000..f936052a37264
--- /dev/null
+++ b/src/plugins/lens_oss/public/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.
+ */
+import { LensOSSPlugin } from './plugin';
+
+export const plugin = () => new LensOSSPlugin();
diff --git a/packages/kbn-ui-framework/generator-kui/app/component.js b/src/plugins/lens_oss/public/plugin.ts
similarity index 53%
rename from packages/kbn-ui-framework/generator-kui/app/component.js
rename to src/plugins/lens_oss/public/plugin.ts
index bcb561f6fa729..15b815dab87c8 100644
--- a/packages/kbn-ui-framework/generator-kui/app/component.js
+++ b/src/plugins/lens_oss/public/plugin.ts
@@ -16,37 +16,27 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { DocLinksStart, CoreSetup } from 'src/core/public';
+import { VisualizationsSetup } from '../../visualizations/public';
+import { getLensAliasConfig } from './vis_type_alias';
-const Generator = require('yeoman-generator');
+export interface LensPluginSetupDependencies {
+ visualizations: VisualizationsSetup;
+}
-const componentGenerator = require.resolve('../component/index.js');
+export interface LensPluginStartDependencies {
+ docLinks: DocLinksStart;
+}
-module.exports = class extends Generator {
- prompting() {
- return this.prompt([
- {
- message: 'What do you want to create?',
- name: 'fileType',
- type: 'list',
- choices: [
- {
- name: 'Stateless function',
- value: 'function',
- },
- {
- name: 'Component class',
- value: 'component',
- },
- ],
- },
- ]).then((answers) => {
- this.config = answers;
+export class LensOSSPlugin {
+ setup(
+ core: CoreSetup,
+ { visualizations }: LensPluginSetupDependencies
+ ) {
+ core.getStartServices().then(([coreStart]) => {
+ visualizations.registerAlias(getLensAliasConfig(coreStart.docLinks));
});
}
- writing() {
- this.composeWith(componentGenerator, {
- fileType: this.config.fileType,
- });
- }
-};
+ start() {}
+}
diff --git a/src/plugins/lens_oss/public/vis_type_alias.ts b/src/plugins/lens_oss/public/vis_type_alias.ts
new file mode 100644
index 0000000000000..230209646deb5
--- /dev/null
+++ b/src/plugins/lens_oss/public/vis_type_alias.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.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { VisTypeAlias } from 'src/plugins/visualizations/public';
+import { DocLinksStart } from 'src/core/public';
+import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common';
+
+export const getLensAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({
+ aliasPath: APP_PATH,
+ aliasApp: APP_NAME,
+ name: PLUGIN_ID_OSS,
+ title: i18n.translate('lensOss.visTypeAlias.title', {
+ defaultMessage: 'Lens',
+ }),
+ description: i18n.translate('lensOss.visTypeAlias.description', {
+ defaultMessage:
+ 'Create visualizations with our drag-and-drop editor. Switch between visualization types at any time. Best for most visualizations.',
+ }),
+ icon: APP_ICON,
+ stage: 'production',
+ disabled: true,
+ note: i18n.translate('lensOss.visTypeAlias.note', {
+ defaultMessage: 'Recommended for most users.',
+ }),
+ promoTooltip: {
+ description: i18n.translate('lensOss.visTypeAlias.promoTooltip.description', {
+ defaultMessage: 'Try Lens for free with Elastic. Learn more.',
+ }),
+ link: `${links.visualize.lens}?blade=kibanaossvizwizard`,
+ },
+});
diff --git a/src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx b/src/plugins/lens_oss/server/index.ts
similarity index 54%
rename from src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx
rename to src/plugins/lens_oss/server/index.ts
index a9837313f2917..1a089a5382cc1 100644
--- a/src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx
+++ b/src/plugins/lens_oss/server/index.ts
@@ -17,25 +17,16 @@
* under the License.
*/
-import { EuiIcon, IconType } from '@elastic/eui';
-import React from 'react';
+import { PluginConfigDescriptor } from 'kibana/server';
+import { copyFromRoot } from '@kbn/config';
+import { configSchema, ConfigSchema } from '../config';
-interface VisTypeIconProps {
- icon?: IconType;
- image?: string;
-}
-
-/**
- * This renders the icon for a specific visualization type.
- * This currently checks the following:
- * - If image is set, use that as the `src` of an image
- * - Otherwise use the icon as an EuiIcon or the 'empty' icon if that's not set
- */
-export const VisTypeIcon = ({ icon, image }: VisTypeIconProps) => {
- return (
-
- {image && }
- {!image && }
-
- );
+export const config: PluginConfigDescriptor = {
+ schema: configSchema,
+ deprecations: () => [copyFromRoot('xpack.lens.enabled', 'lens_oss.enabled')],
};
+
+export const plugin = () => ({
+ setup() {},
+ start() {},
+});
diff --git a/src/plugins/maps_oss/README.md b/src/plugins/maps_oss/README.md
new file mode 100644
index 0000000000000..ed91de500fbfb
--- /dev/null
+++ b/src/plugins/maps_oss/README.md
@@ -0,0 +1,6 @@
+# maps_oss
+
+The maps_oss plugin registers the maps visualization on OSS.
+It is registered as disabled. The x-pack plugin should unregister this.
+
+`visualizations.unregisterAlias('mapsOss')`
\ No newline at end of file
diff --git a/src/plugins/maps_oss/common/constants.ts b/src/plugins/maps_oss/common/constants.ts
new file mode 100644
index 0000000000000..b4063c68840b3
--- /dev/null
+++ b/src/plugins/maps_oss/common/constants.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 APP_NAME = 'maps';
+export const PLUGIN_ID_OSS = 'mapsOss';
+export const APP_PATH = '/map';
+export const APP_ICON = 'gisApp';
diff --git a/src/plugins/maps_oss/common/index.ts b/src/plugins/maps_oss/common/index.ts
new file mode 100644
index 0000000000000..fd1c2843d6b26
--- /dev/null
+++ b/src/plugins/maps_oss/common/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 './constants';
diff --git a/src/plugins/maps_oss/config.ts b/src/plugins/maps_oss/config.ts
new file mode 100644
index 0000000000000..6749bd83de39f
--- /dev/null
+++ b/src/plugins/maps_oss/config.ts
@@ -0,0 +1,26 @@
+/*
+ * 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 { schema, TypeOf } from '@kbn/config-schema';
+
+export const configSchema = schema.object({
+ enabled: schema.boolean({ defaultValue: true }),
+});
+
+export type ConfigSchema = TypeOf;
diff --git a/src/plugins/maps_oss/kibana.json b/src/plugins/maps_oss/kibana.json
new file mode 100644
index 0000000000000..19770dcffaadd
--- /dev/null
+++ b/src/plugins/maps_oss/kibana.json
@@ -0,0 +1,10 @@
+{
+ "id": "mapsOss",
+ "version": "kibana",
+ "ui": true,
+ "server": true,
+ "requiredPlugins": [
+ "visualizations"
+ ],
+ "extraPublicDirs": ["common/constants"]
+}
diff --git a/src/plugins/maps_oss/public/index.ts b/src/plugins/maps_oss/public/index.ts
new file mode 100644
index 0000000000000..ec18ff1fde638
--- /dev/null
+++ b/src/plugins/maps_oss/public/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.
+ */
+import { MapsOSSPlugin } from './plugin';
+
+export const plugin = () => new MapsOSSPlugin();
diff --git a/src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx b/src/plugins/maps_oss/public/plugin.ts
similarity index 53%
rename from src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx
rename to src/plugins/maps_oss/public/plugin.ts
index 8517919955f7c..3369e0bf27706 100644
--- a/src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx
+++ b/src/plugins/maps_oss/public/plugin.ts
@@ -17,34 +17,27 @@
* under the License.
*/
-import React from 'react';
+import { DocLinksStart, CoreSetup } from 'src/core/public';
+import { VisualizationsSetup } from '../../visualizations/public';
+import { getMapsAliasConfig } from './vis_type_alias';
-import { EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
+export interface MapsPluginSetupDependencies {
+ visualizations: VisualizationsSetup;
+}
-interface VisHelpTextProps {
- name: string;
- title: string;
- description?: string;
- highlightMsg?: string;
+export interface MapsPluginStartDependencies {
+ docLinks: DocLinksStart;
}
-export const VisHelpText = ({ name, title, description, highlightMsg }: VisHelpTextProps) => {
- return (
-
-
- {title}
-
-
-
-
- {highlightMsg && (
-
- {highlightMsg}
-
- )}
- {description}
-
-
-
- );
-};
+export class MapsOSSPlugin {
+ setup(
+ core: CoreSetup,
+ { visualizations }: MapsPluginSetupDependencies
+ ) {
+ core.getStartServices().then(([coreStart]) => {
+ visualizations.registerAlias(getMapsAliasConfig(coreStart.docLinks));
+ });
+ }
+
+ start() {}
+}
diff --git a/src/plugins/maps_oss/public/vis_type_alias.ts b/src/plugins/maps_oss/public/vis_type_alias.ts
new file mode 100644
index 0000000000000..14fdc06bc539f
--- /dev/null
+++ b/src/plugins/maps_oss/public/vis_type_alias.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { VisTypeAlias } from 'src/plugins/visualizations/public';
+import { DocLinksStart } from 'src/core/public';
+import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common';
+
+export const getMapsAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({
+ aliasPath: APP_PATH,
+ aliasApp: APP_NAME,
+ name: PLUGIN_ID_OSS,
+ title: i18n.translate('mapsOss.visTypeAlias.title', {
+ defaultMessage: 'Maps',
+ }),
+ description: i18n.translate('mapsOss.visTypeAlias.description', {
+ defaultMessage: 'Plot and style your geo data in a multi layer map.',
+ }),
+ icon: APP_ICON,
+ stage: 'production',
+ disabled: true,
+ promoTooltip: {
+ description: i18n.translate('mapsOss.visTypeAlias.promoTooltip.description', {
+ defaultMessage: 'Try maps for free with Elastic. Learn more.',
+ }),
+ link: `${links.visualize.maps}?blade=kibanaossvizwizard`,
+ },
+});
diff --git a/src/plugins/maps_oss/server/index.ts b/src/plugins/maps_oss/server/index.ts
new file mode 100644
index 0000000000000..defc3a09c2538
--- /dev/null
+++ b/src/plugins/maps_oss/server/index.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { PluginConfigDescriptor } from 'kibana/server';
+import { copyFromRoot } from '@kbn/config';
+import { configSchema, ConfigSchema } from '../config';
+
+export const config: PluginConfigDescriptor = {
+ schema: configSchema,
+ deprecations: () => [copyFromRoot('xpack.maps.enabled', 'maps_oss.enabled')],
+};
+
+export const plugin = () => ({
+ setup() {},
+ start() {},
+});
diff --git a/src/plugins/region_map/config.ts b/src/plugins/region_map/config.ts
index a721a76ca0a82..d60831563ed2b 100644
--- a/src/plugins/region_map/config.ts
+++ b/src/plugins/region_map/config.ts
@@ -19,28 +19,29 @@
import { schema, TypeOf } from '@kbn/config-schema';
-export const configSchema = schema.object({
- includeElasticMapsService: schema.boolean({ defaultValue: true }),
- layers: schema.arrayOf(
+const layerConfigSchema = schema.object({
+ url: schema.string(),
+ format: schema.object({
+ type: schema.string({ defaultValue: 'geojson' }),
+ }),
+ meta: schema.object({
+ feature_collection_path: schema.string({ defaultValue: 'data' }),
+ }),
+ attribution: schema.string(),
+ name: schema.string(),
+ fields: schema.arrayOf(
schema.object({
- url: schema.string(),
- format: schema.object({
- type: schema.string({ defaultValue: 'geojson' }),
- }),
- meta: schema.object({
- feature_collection_path: schema.string({ defaultValue: 'data' }),
- }),
- attribution: schema.string(),
name: schema.string(),
- fields: schema.arrayOf(
- schema.object({
- name: schema.string(),
- description: schema.string(),
- })
- ),
- }),
- { defaultValue: [] }
+ description: schema.string(),
+ })
),
});
+export const configSchema = schema.object({
+ includeElasticMapsService: schema.boolean({ defaultValue: true }),
+ layers: schema.arrayOf(layerConfigSchema, { defaultValue: [] }),
+});
+
+export type LayerConfig = TypeOf;
+
export type ConfigSchema = TypeOf;
diff --git a/src/plugins/saved_objects/README.md b/src/plugins/saved_objects/README.md
new file mode 100644
index 0000000000000..2f8dd44a2c5fa
--- /dev/null
+++ b/src/plugins/saved_objects/README.md
@@ -0,0 +1,3 @@
+# `savedObjects` plugin
+
+The `savedObjects` plugin exposes utilities to manipulate saved objects on the client side.
\ No newline at end of file
diff --git a/src/plugins/saved_objects_management/README.md b/src/plugins/saved_objects_management/README.md
new file mode 100644
index 0000000000000..cdaf027e7d2de
--- /dev/null
+++ b/src/plugins/saved_objects_management/README.md
@@ -0,0 +1,3 @@
+# `savedObjectsManagement` plugin
+
+The `savedObjectsManagement` plugin manages the `Saved Objects` management section.
diff --git a/src/plugins/vis_type_markdown/public/markdown_vis.ts b/src/plugins/vis_type_markdown/public/markdown_vis.ts
index 27ac038aee6ff..b6ec2cbd993b0 100644
--- a/src/plugins/vis_type_markdown/public/markdown_vis.ts
+++ b/src/plugins/vis_type_markdown/public/markdown_vis.ts
@@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n';
import { MarkdownOptions } from './markdown_options';
import { SettingsOptions } from './settings_options_lazy';
import { DefaultEditorSize } from '../../vis_default_editor/public';
+import { VisGroups } from '../../visualizations/public';
import { toExpressionAst } from './to_ast';
export const markdownVisDefinition = {
@@ -29,8 +30,12 @@ export const markdownVisDefinition = {
title: 'Markdown',
isAccessible: true,
icon: 'visText',
+ group: VisGroups.TOOLS,
+ titleInWizard: i18n.translate('visTypeMarkdown.markdownTitleInWizard', {
+ defaultMessage: 'Text',
+ }),
description: i18n.translate('visTypeMarkdown.markdownDescription', {
- defaultMessage: 'Create a document using markdown syntax',
+ defaultMessage: 'Add text and images to your dashboard.',
}),
toExpressionAst,
visConfig: {
diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts
index 4520069244527..8973060848b41 100644
--- a/src/plugins/vis_type_timeseries/common/types.ts
+++ b/src/plugins/vis_type_timeseries/common/types.ts
@@ -18,8 +18,9 @@
*/
import { TypeOf } from '@kbn/config-schema';
-import { metricsItems, panel, seriesItems } from './vis_schema';
+import { metricsItems, panel, seriesItems, visPayloadSchema } from './vis_schema';
export type SeriesItemsSchema = TypeOf;
export type MetricsItemsSchema = TypeOf;
export type PanelSchema = TypeOf;
+export type VisPayload = TypeOf;
diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts
index 40f776050617e..27f09fb574b0f 100644
--- a/src/plugins/vis_type_timeseries/common/vis_schema.ts
+++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts
@@ -273,4 +273,5 @@ export const visPayloadSchema = schema.object({
min: stringRequired,
max: stringRequired,
}),
+ sessionId: schema.maybe(schema.string()),
});
diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts
index d6621870fef67..682517ab1a996 100644
--- a/src/plugins/vis_type_timeseries/public/metrics_type.ts
+++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts
@@ -25,15 +25,16 @@ import { EditorController } from './application';
// @ts-ignore
import { PANEL_TYPES } from '../common/panel_types';
import { VisEditor } from './application/components/vis_editor_lazy';
-import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
+import { VIS_EVENT_TO_TRIGGER, VisGroups } from '../../visualizations/public';
export const metricsVisDefinition = {
name: 'metrics',
title: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsTitle', { defaultMessage: 'TSVB' }),
description: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsDescription', {
- defaultMessage: 'Build time-series using a visual pipeline interface',
+ defaultMessage: 'Perform advanced analysis of your time series data.',
}),
icon: 'visVisualBuilder',
+ group: VisGroups.PROMOTED,
visConfig: {
defaults: {
id: '61ca57f0-469d-11e7-af02-69e470af7417',
diff --git a/src/plugins/vis_type_timeseries/public/request_handler.js b/src/plugins/vis_type_timeseries/public/request_handler.js
index e33d0e254f609..12b7f3d417ef6 100644
--- a/src/plugins/vis_type_timeseries/public/request_handler.js
+++ b/src/plugins/vis_type_timeseries/public/request_handler.js
@@ -32,7 +32,8 @@ export const metricsRequestHandler = async ({
const config = getUISettings();
const timezone = getTimezone(config);
const uiStateObj = uiState.get(visParams.type, {});
- const parsedTimeRange = getDataStart().query.timefilter.timefilter.calculateBounds(timeRange);
+ const dataSearch = getDataStart();
+ const parsedTimeRange = dataSearch.query.timefilter.timefilter.calculateBounds(timeRange);
const scaledDataFormat = config.get('dateFormat:scaled');
const dateFormat = config.get('dateFormat');
@@ -53,6 +54,7 @@ export const metricsRequestHandler = async ({
panels: [visParams],
state: uiStateObj,
savedObjectId: savedObjectId || 'unsaved',
+ sessionId: dataSearch.search.session.getSessionId(),
}),
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js
index 9710f7daf69b6..2c38e883cd69f 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js
@@ -28,6 +28,7 @@ describe('AbstractSearchStrategy', () => {
beforeEach(() => {
mockedFields = {};
req = {
+ payload: {},
pre: {
indexPatternsService: {
getFieldsForWildcard: jest.fn().mockReturnValue(mockedFields),
@@ -60,6 +61,9 @@ describe('AbstractSearchStrategy', () => {
const responses = await abstractSearchStrategy.search(
{
+ payload: {
+ sessionId: 1,
+ },
requestContext: {
search: { search: searchFn },
},
@@ -76,7 +80,9 @@ describe('AbstractSearchStrategy', () => {
},
indexType: undefined,
},
- {}
+ {
+ sessionId: 1,
+ }
);
});
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
index eb22fcb1dd689..b1e21edf8b588 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
@@ -23,8 +23,10 @@ import {
IUiSettingsClient,
SavedObjectsClientContract,
} from 'kibana/server';
+
import { Framework } from '../../../plugin';
import { IndexPatternsFetcher } from '../../../../../data/server';
+import { VisPayload } from '../../../../common/types';
/**
* ReqFacade is a regular KibanaRequest object extended with additional service
@@ -32,17 +34,17 @@ import { IndexPatternsFetcher } from '../../../../../data/server';
*
* This will be replaced by standard KibanaRequest and RequestContext objects in a later version.
*/
-export type ReqFacade = FakeRequest & {
+export interface ReqFacade extends FakeRequest {
requestContext: RequestHandlerContext;
framework: Framework;
- payload: unknown;
+ payload: T;
pre: {
indexPatternsService?: IndexPatternsFetcher;
};
getUiSettingsService: () => IUiSettingsClient;
getSavedObjectsClient: () => SavedObjectsClientContract;
getEsShardTimeout: () => Promise;
-};
+}
export class AbstractSearchStrategy {
public indexType?: string;
@@ -53,8 +55,10 @@ export class AbstractSearchStrategy {
this.additionalParams = additionalParams;
}
- async search(req: ReqFacade, bodies: any[], options = {}) {
+ async search(req: ReqFacade, bodies: any[], options = {}) {
const requests: any[] = [];
+ const { sessionId } = req.payload;
+
bodies.forEach((body) => {
requests.push(
req.requestContext
@@ -67,6 +71,7 @@ export class AbstractSearchStrategy {
indexType: this.indexType,
},
{
+ sessionId,
...options,
}
)
diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts
index 17f35b75f0016..2211abb54aa93 100644
--- a/src/plugins/vis_type_vega/public/vega_type.ts
+++ b/src/plugins/vis_type_vega/public/vega_type.ts
@@ -25,7 +25,7 @@ import { VegaVisualizationDependencies } from './plugin';
import { createVegaRequestHandler } from './vega_request_handler';
import { getDefaultSpec } from './default_spec';
import { createInspectorAdapters } from './vega_inspector';
-import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
+import { VIS_EVENT_TO_TRIGGER, VisGroups } from '../../visualizations/public';
import { toExpressionAst } from './to_ast';
import { VisParams } from './vega_fn';
import { getInfoMessage } from './components/experimental_map_vis_info';
@@ -41,10 +41,17 @@ export const createVegaTypeDefinition = (
title: 'Vega',
getInfoMessage,
description: i18n.translate('visTypeVega.type.vegaDescription', {
- defaultMessage: 'Create custom visualizations using Vega and Vega-Lite',
+ defaultMessage: 'Use Vega to create new types of visualizations.',
description: 'Vega and Vega-Lite are product names and should not be translated',
}),
+ note: i18n.translate('visTypeVega.type.vegaNote', {
+ defaultMessage: 'Requires knowledge of Vega syntax.',
+ }),
icon: 'visVega',
+ group: VisGroups.PROMOTED,
+ titleInWizard: i18n.translate('visTypeVega.type.vegaTitleInWizard', {
+ defaultMessage: 'Custom visualization',
+ }),
visConfig: { defaults: { spec: getDefaultSpec() } },
editorConfig: {
optionsTemplate: VegaVisEditorComponent,
diff --git a/src/plugins/vis_type_vislib/public/gauge.ts b/src/plugins/vis_type_vislib/public/gauge.ts
index 86e3b8793d618..2b3c415087ee1 100644
--- a/src/plugins/vis_type_vislib/public/gauge.ts
+++ b/src/plugins/vis_type_vislib/public/gauge.ts
@@ -61,8 +61,7 @@ export const gaugeVisTypeDefinition: BaseVisTypeOptions = {
title: i18n.translate('visTypeVislib.gauge.gaugeTitle', { defaultMessage: 'Gauge' }),
icon: 'visGauge',
description: i18n.translate('visTypeVislib.gauge.gaugeDescription', {
- defaultMessage:
- "Gauges indicate the status of a metric. Use it to show how a metric's value relates to reference threshold values.",
+ defaultMessage: 'Gauges indicate the status of a metric.',
}),
toExpressionAst,
visConfig: {
diff --git a/src/plugins/visualizations/public/index.scss b/src/plugins/visualizations/public/index.scss
index 2b61535f3e7f2..0202419cea232 100644
--- a/src/plugins/visualizations/public/index.scss
+++ b/src/plugins/visualizations/public/index.scss
@@ -1,3 +1,2 @@
-@import 'wizard/index';
@import 'embeddable/index';
@import 'components/index';
diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts
index 7bd4466b23166..d66a6f6113cad 100644
--- a/src/plugins/visualizations/public/index.ts
+++ b/src/plugins/visualizations/public/index.ts
@@ -36,6 +36,7 @@ export { getSchemas as getVisSchemas } from './legacy/build_pipeline';
/** @public types */
export { VisualizationsSetup, VisualizationsStart };
+export { VisGroups } from './vis_types';
export type { VisTypeAlias, VisType, BaseVisTypeOptions, ReactVisTypeOptions } from './vis_types';
export { VisParams, SerializedVis, SerializedVisData, VisData } from './vis';
export type VisualizeEmbeddableFactoryContract = PublicContract;
diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts
index f20e87dbd3b6a..66399352bea7d 100644
--- a/src/plugins/visualizations/public/mocks.ts
+++ b/src/plugins/visualizations/public/mocks.ts
@@ -41,6 +41,8 @@ const createStartContract = (): VisualizationsStart => ({
get: jest.fn(),
all: jest.fn(),
getAliases: jest.fn(),
+ getByGroup: jest.fn(),
+ unRegisterAlias: jest.fn(),
savedVisualizationsLoader: {
get: jest.fn(),
} as any,
diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts
index c1dbe39def64c..29e31e92b971e 100644
--- a/src/plugins/visualizations/public/plugin.ts
+++ b/src/plugins/visualizations/public/plugin.ts
@@ -49,6 +49,7 @@ import {
setOverlays,
setSavedSearchLoader,
setEmbeddable,
+ setDocLinks,
} from './services';
import {
VISUALIZE_EMBEDDABLE_TYPE,
@@ -172,6 +173,7 @@ export class VisualizationsPlugin
setCapabilities(core.application.capabilities);
setHttp(core.http);
setSavedObjects(core.savedObjects);
+ setDocLinks(core.docLinks);
setIndexPatterns(data.indexPatterns);
setSearch(data.search);
setFilterManager(data.query.filterManager);
diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts
index 0761b8862e8e3..08537b4ac3081 100644
--- a/src/plugins/visualizations/public/services.ts
+++ b/src/plugins/visualizations/public/services.ts
@@ -26,6 +26,7 @@ import {
IUiSettingsClient,
OverlayStart,
SavedObjectsStart,
+ DocLinksStart,
} from '../../../core/public';
import { TypesStart } from './vis_types';
import { createGetterSetter } from '../../../plugins/kibana_utils/public';
@@ -60,6 +61,8 @@ export const [getTypes, setTypes] = createGetterSetter('Types');
export const [getI18n, setI18n] = createGetterSetter('I18n');
+export const [getDocLinks, setDocLinks] = createGetterSetter('DocLinks');
+
export const [getFilterManager, setFilterManager] = createGetterSetter(
'FilterManager'
);
diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts
index f2933de723a39..807582723172d 100644
--- a/src/plugins/visualizations/public/vis_types/base_vis_type.ts
+++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts
@@ -20,7 +20,7 @@
import { defaultsDeep } from 'lodash';
import { ISchemas } from 'src/plugins/vis_default_editor/public';
import { VisParams } from '../types';
-import { VisType, VisTypeOptions } from './types';
+import { VisType, VisTypeOptions, VisGroups } from './types';
interface CommonBaseVisTypeOptions
extends Pick<
@@ -41,7 +41,14 @@ interface CommonBaseVisTypeOptions
>,
Pick<
Partial>,
- 'editorConfig' | 'hidden' | 'stage' | 'useCustomNoDataScreen' | 'visConfig'
+ | 'editorConfig'
+ | 'hidden'
+ | 'stage'
+ | 'useCustomNoDataScreen'
+ | 'visConfig'
+ | 'group'
+ | 'titleInWizard'
+ | 'note'
> {
options?: Partial['options']>;
}
@@ -72,10 +79,13 @@ export class BaseVisType implements VisType
public readonly name;
public readonly title;
public readonly description;
+ public readonly note;
public readonly getSupportedTriggers;
public readonly icon;
public readonly image;
public readonly stage;
+ public readonly group;
+ public readonly titleInWizard;
public readonly options;
public readonly visualization;
public readonly visConfig;
@@ -98,6 +108,7 @@ export class BaseVisType implements VisType
this.name = opts.name;
this.description = opts.description ?? '';
+ this.note = opts.note ?? '';
this.getSupportedTriggers = opts.getSupportedTriggers;
this.title = opts.title;
this.icon = opts.icon;
@@ -108,6 +119,8 @@ export class BaseVisType implements VisType
this.editorConfig = defaultsDeep({}, opts.editorConfig, { collections: {} });
this.options = defaultsDeep({}, opts.options, defaultOptions);
this.stage = opts.stage ?? 'production';
+ this.group = opts.group ?? VisGroups.AGGBASED;
+ this.titleInWizard = opts.titleInWizard ?? '';
this.hidden = opts.hidden ?? false;
this.requestHandler = opts.requestHandler ?? 'courier';
this.responseHandler = opts.responseHandler ?? 'none';
diff --git a/src/plugins/visualizations/public/vis_types/index.ts b/src/plugins/visualizations/public/vis_types/index.ts
index a46b257c9905c..a02ac82c8d122 100644
--- a/src/plugins/visualizations/public/vis_types/index.ts
+++ b/src/plugins/visualizations/public/vis_types/index.ts
@@ -18,6 +18,7 @@
*/
export * from './types_service';
+export { VisGroups } from './types';
export type { VisType } from './types';
export type { BaseVisTypeOptions } from './base_vis_type';
export type { ReactVisTypeOptions } from './react_vis_type';
diff --git a/src/plugins/visualizations/public/vis_types/types.ts b/src/plugins/visualizations/public/vis_types/types.ts
index 7206e9612f102..ee804e5677243 100644
--- a/src/plugins/visualizations/public/vis_types/types.ts
+++ b/src/plugins/visualizations/public/vis_types/types.ts
@@ -33,21 +33,63 @@ export interface VisTypeOptions {
hierarchicalData: boolean;
}
+export enum VisGroups {
+ PROMOTED = 'promoted',
+ TOOLS = 'tools',
+ AGGBASED = 'aggbased',
+}
+
/**
* A visualization type representing one specific type of "classical"
* visualizations (i.e. not Lens visualizations).
*/
export interface VisType {
+ /**
+ * Visualization unique name
+ */
readonly name: string;
+ /**
+ * It is the displayed text on the wizard and the vis listing
+ */
readonly title: string;
+ /**
+ * If given, it will be diplayed on the wizard vis card as the main description.
+ */
readonly description?: string;
+ /**
+ * If given, it will be diplayed on the wizard vis card as a note in italic.
+ */
+ readonly note: string;
+ /**
+ * If given, it will return the supported triggers for this vis.
+ */
readonly getSupportedTriggers?: () => Array;
readonly isAccessible?: boolean;
readonly requestHandler?: string | unknown;
readonly responseHandler?: string | unknown;
+ /**
+ * It is the visualization icon, displayed on the wizard.
+ */
readonly icon?: IconType;
+ /**
+ * Except from an icon, an image can be passed
+ */
readonly image?: string;
+ /**
+ * Describes the visualization stage
+ */
readonly stage: 'experimental' | 'beta' | 'production';
+ /**
+ * Describes the experience group that the visualization belongs.
+ * It can be on tools, aggregation based or promoted group.
+ */
+ readonly group: VisGroups;
+ /**
+ * If given, it will be displayed on the wizard instead of the title.
+ * We use it because we want to differentiate the vis title from the
+ * way it is presented on the wizard
+ */
+ readonly titleInWizard: string;
readonly requiresSearch: boolean;
readonly useCustomNoDataScreen: boolean;
readonly hierarchicalData?: boolean | ((vis: { params: TVisParams }) => boolean);
diff --git a/src/plugins/visualizations/public/vis_types/types_service.ts b/src/plugins/visualizations/public/vis_types/types_service.ts
index 5d619064c240e..27276eeb88923 100644
--- a/src/plugins/visualizations/public/vis_types/types_service.ts
+++ b/src/plugins/visualizations/public/vis_types/types_service.ts
@@ -20,7 +20,7 @@
import { visTypeAliasRegistry, VisTypeAlias } from './vis_type_alias_registry';
import { BaseVisType, BaseVisTypeOptions } from './base_vis_type';
import { ReactVisType, ReactVisTypeOptions } from './react_vis_type';
-import { VisType } from './types';
+import { VisType, VisGroups } from './types';
/**
* Vis Types Service
@@ -101,6 +101,21 @@ export class TypesService {
* returns all registered aliases
*/
getAliases: visTypeAliasRegistry.get,
+ /**
+ * unregisters a visualization alias by its name
+ * alias is a visualization type without implementation, it just redirects somewhere in kibana
+ * @param {string} visTypeAliasName - visualization alias name
+ */
+ unRegisterAlias: visTypeAliasRegistry.remove,
+ /**
+ * returns all visualizations of specific group
+ * @param {VisGroups} group - group type (aggbased | other | tools)
+ */
+ getByGroup: (group: VisGroups) => {
+ return Object.values(this.types).filter((type) => {
+ return type.group === group;
+ });
+ },
};
}
diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts
index 9733e9fd68549..fc5dfd4e123fb 100644
--- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts
+++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts
@@ -43,9 +43,9 @@ export interface VisualizationsAppExtension {
}) => VisualizationListItem;
}
-export interface VisTypeAliasPromotion {
+export interface VisTypeAliasPromoTooltip {
description: string;
- buttonText: string;
+ link: string;
}
export interface VisTypeAlias {
@@ -54,8 +54,11 @@ export interface VisTypeAlias {
name: string;
title: string;
icon: string;
- promotion?: VisTypeAliasPromotion;
+ promotion?: boolean;
+ promoTooltip?: VisTypeAliasPromoTooltip;
description: string;
+ note?: string;
+ disabled?: boolean;
getSupportedTriggers?: () => Array;
stage: 'experimental' | 'beta' | 'production';
@@ -65,11 +68,13 @@ export interface VisTypeAlias {
};
}
-const registry: VisTypeAlias[] = [];
+let registry: VisTypeAlias[] = [];
+const discardOnRegister: string[] = [];
interface VisTypeAliasRegistry {
get: () => VisTypeAlias[];
add: (newVisTypeAlias: VisTypeAlias) => void;
+ remove: (visTypeAliasName: string) => void;
}
export const visTypeAliasRegistry: VisTypeAliasRegistry = {
@@ -78,6 +83,22 @@ export const visTypeAliasRegistry: VisTypeAliasRegistry = {
if (registry.find((visTypeAlias) => visTypeAlias.name === newVisTypeAlias.name)) {
throw new Error(`${newVisTypeAlias.name} already registered`);
}
- registry.push(newVisTypeAlias);
+ // if it exists on discardOnRegister array then we don't allow it to be registered
+ const isToBeDiscarded = discardOnRegister.some(
+ (aliasName) => aliasName === newVisTypeAlias.name
+ );
+ if (!isToBeDiscarded) {
+ registry.push(newVisTypeAlias);
+ }
+ },
+ remove: (visTypeAliasName) => {
+ const isAliasPresent = registry.find((visTypeAlias) => visTypeAlias.name === visTypeAliasName);
+ // in case the alias has not registered yet we store it on an array, in order to not allow it to
+ // be registered in case of a race condition
+ if (!isAliasPresent) {
+ discardOnRegister.push(visTypeAliasName);
+ } else {
+ registry = registry.filter((visTypeAlias) => visTypeAlias.name !== visTypeAliasName);
+ }
},
};
diff --git a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap
deleted file mode 100644
index 2089289b372a2..0000000000000
--- a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap
+++ /dev/null
@@ -1,2141 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`NewVisModal filter for visualization types should render as expected 1`] = `
-
-
-
-
-
-`;
-
-exports[`NewVisModal should render as expected 1`] = `
-
-
-
-
-
-`;
diff --git a/src/plugins/visualizations/public/wizard/_index.scss b/src/plugins/visualizations/public/wizard/_index.scss
deleted file mode 100644
index a10b4b1b347b7..0000000000000
--- a/src/plugins/visualizations/public/wizard/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import 'dialog';
diff --git a/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx
new file mode 100644
index 0000000000000..3cbe6a0b604c6
--- /dev/null
+++ b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx
@@ -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 React from 'react';
+import { mountWithIntl } from 'test_utils/enzyme_helpers';
+import { TypesStart, VisType, VisGroups } from '../../vis_types';
+import { AggBasedSelection } from './agg_based_selection';
+
+describe('AggBasedSelection', () => {
+ const defaultVisTypeParams = {
+ hidden: false,
+ visualization: class Controller {
+ public render = jest.fn();
+ public destroy = jest.fn();
+ },
+ requiresSearch: false,
+ requestHandler: 'none',
+ responseHandler: 'none',
+ };
+ const _visTypes = [
+ {
+ name: 'vis1',
+ title: 'Vis Type 1',
+ stage: 'production',
+ group: VisGroups.PROMOTED,
+ ...defaultVisTypeParams,
+ },
+ {
+ name: 'vis2',
+ title: 'Vis Type 2',
+ group: VisGroups.AGGBASED,
+ stage: 'production',
+ ...defaultVisTypeParams,
+ },
+ {
+ name: 'vis3',
+ title: 'Vis Type 3',
+ stage: 'production',
+ group: VisGroups.AGGBASED,
+ },
+ {
+ name: 'visWithSearch',
+ title: 'Vis with search',
+ group: VisGroups.AGGBASED,
+ stage: 'production',
+ ...defaultVisTypeParams,
+ },
+ ] as VisType[];
+
+ const visTypes: TypesStart = {
+ get(id: string): VisType {
+ return _visTypes.find((vis) => vis.name === id) as VisType;
+ },
+ all: () => {
+ return (_visTypes as unknown) as VisType[];
+ },
+ getAliases: () => [],
+ unRegisterAlias: () => [],
+ getByGroup: (group: VisGroups) => {
+ return _visTypes.filter((type) => {
+ return type.group === group;
+ }) as VisType[];
+ },
+ };
+
+ beforeAll(() => {
+ Object.defineProperty(window, 'location', {
+ value: {
+ assign: jest.fn(),
+ },
+ });
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should call the toggleGroups if the user clicks the goBack link', () => {
+ const toggleGroups = jest.fn();
+ const wrapper = mountWithIntl(
+
+ );
+ const aggBasedGroupCard = wrapper.find('[data-test-subj="goBackLink"]').at(0);
+ aggBasedGroupCard.simulate('click');
+ expect(toggleGroups).toHaveBeenCalled();
+ });
+
+ describe('filter for visualization types', () => {
+ it('should render as expected', () => {
+ const wrapper = mountWithIntl(
+
+ );
+ const searchBox = wrapper.find('input[data-test-subj="filterVisType"]');
+ searchBox.simulate('change', { target: { value: 'with' } });
+ expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(true);
+ });
+ });
+});
diff --git a/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx
new file mode 100644
index 0000000000000..5d11923ab6830
--- /dev/null
+++ b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx
@@ -0,0 +1,163 @@
+/*
+ * 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 { FormattedMessage } from '@kbn/i18n/react';
+import { orderBy } from 'lodash';
+import React, { ChangeEvent } from 'react';
+
+import {
+ EuiFieldSearch,
+ EuiFlexGrid,
+ EuiFlexItem,
+ EuiScreenReaderOnly,
+ EuiSpacer,
+ EuiIcon,
+ EuiCard,
+ EuiModalBody,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+} from '@elastic/eui';
+
+import { memoizeLast } from '../../legacy/memoize';
+import type { VisType, TypesStart } from '../../vis_types';
+import { VisGroups } from '../../vis_types';
+import { DialogNavigation } from '../dialog_navigation';
+
+interface VisTypeListEntry {
+ type: VisType;
+ highlighted: boolean;
+}
+
+interface AggBasedSelectionProps {
+ onVisTypeSelected: (visType: VisType) => void;
+ visTypesRegistry: TypesStart;
+ toggleGroups: (flag: boolean) => void;
+}
+interface AggBasedSelectionState {
+ query: string;
+}
+
+class AggBasedSelection extends React.Component {
+ public state: AggBasedSelectionState = {
+ query: '',
+ };
+
+ private readonly getFilteredVisTypes = memoizeLast(this.filteredVisTypes);
+
+ public render() {
+ const { query } = this.state;
+ const visTypes = this.getFilteredVisTypes(this.props.visTypesRegistry, query);
+ return (
+ <>
+
+
+
+
+
+
+ this.props.toggleGroups(true)} />
+
+
+
+
+ {query && (
+ type.highlighted).length,
+ }}
+ />
+ )}
+
+
+
+ {visTypes.map(this.renderVisType)}
+
+
+ >
+ );
+ }
+
+ private filteredVisTypes(visTypes: TypesStart, query: string): VisTypeListEntry[] {
+ const types = visTypes.getByGroup(VisGroups.AGGBASED).filter((type) => {
+ // Filter out hidden visualizations and visualizations that are only aggregations based
+ return !type.hidden;
+ });
+
+ let entries: VisTypeListEntry[];
+ if (!query) {
+ entries = types.map((type) => ({ type, highlighted: false }));
+ } else {
+ const q = query.toLowerCase();
+ entries = types.map((type) => {
+ const matchesQuery =
+ type.name.toLowerCase().includes(q) ||
+ type.title.toLowerCase().includes(q) ||
+ (typeof type.description === 'string' && type.description.toLowerCase().includes(q));
+ return { type, highlighted: matchesQuery };
+ });
+ }
+
+ return orderBy(entries, ['highlighted', 'type.title'], ['desc', 'asc']);
+ }
+
+ private renderVisType = (visType: VisTypeListEntry) => {
+ const isDisabled = this.state.query !== '' && !visType.highlighted;
+ const onClick = () => this.props.onVisTypeSelected(visType.type);
+
+ return (
+
+ {visType.type.title}}
+ onClick={onClick}
+ data-test-subj={`visType-${visType.type.name}`}
+ data-vis-stage={visType.type.stage}
+ aria-label={`visType-${visType.type.name}`}
+ description={visType.type.description || ''}
+ layout="horizontal"
+ isDisabled={isDisabled}
+ icon={ }
+ />
+
+ );
+ };
+
+ private onQueryChange = (ev: ChangeEvent) => {
+ this.setState({
+ query: ev.target.value,
+ });
+ };
+}
+
+export { AggBasedSelection };
diff --git a/src/plugins/visualizations/public/wizard/agg_based_selection/index.ts b/src/plugins/visualizations/public/wizard/agg_based_selection/index.ts
new file mode 100644
index 0000000000000..c1dfab71cce49
--- /dev/null
+++ b/src/plugins/visualizations/public/wizard/agg_based_selection/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 { AggBasedSelection } from './agg_based_selection';
diff --git a/src/plugins/visualizations/public/wizard/_dialog.scss b/src/plugins/visualizations/public/wizard/dialog.scss
similarity index 81%
rename from src/plugins/visualizations/public/wizard/_dialog.scss
rename to src/plugins/visualizations/public/wizard/dialog.scss
index 793951f9dd1ca..f0cc8381a67aa 100644
--- a/src/plugins/visualizations/public/wizard/_dialog.scss
+++ b/src/plugins/visualizations/public/wizard/dialog.scss
@@ -1,84 +1,57 @@
-.visNewVisDialog {
- max-width: 100vw;
- background-image: url(lightOrDarkTheme("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%23F5F7FA' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%23E6EBF2' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E","data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%2318191E' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%2315161B' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E"));
- background-repeat: no-repeat;
- background-position: calc(100% + 1px) calc(100% + 1px);
- background-size: 30%;
-}
+$modalWidth: $euiSizeL * 34;
+$modalHeight: $euiSizeL * 30;
-.visNewVisSearchDialog {
- width: $euiSizeL * 30;
- min-height: $euiSizeL * 25;
-}
-
-.visNewVisDialog__body {
- display: flex;
- padding: $euiSizeM $euiSizeL 0;
- min-height: 0;
-}
-.visNewVisDialog__list {
- min-height: 0;
-}
+.visNewVisDialog {
+ @include euiBreakpoint('xs', 's') {
+ max-height: 100%;
+ }
-.visNewVisDialog__searchWrapper {
- flex-shrink: 0;
+ max-width: $modalWidth;
+ max-height: $modalHeight;
+ background: $euiColorEmptyShade;
}
-.visNewVisDialog__typesWrapper {
- @include euiOverflowShadow;
- max-width: $euiSizeXXL * 10;
- min-height: 0;
- margin-top: 2px; // Account for search field dropshadow
- overflow: hidden;
-}
+.visNewVisDialog--aggbased {
+ @include euiBreakpoint('xs', 's') {
+ max-height: 100%;
+ }
-.visNewVisDialog__types {
- @include euiScrollBar;
- // EUITODO: allow for more (calculated) widths of `EuiKeyPadMenu`
- width: auto;
- overflow-y: auto;
- padding-top: $euiSize;
- justify-content: center;
- padding-bottom: $euiSize;
+ max-width: $modalWidth;
+ max-height: $modalHeight;
+ background-repeat: no-repeat;
+ background-position: calc(100% + 1px) calc(100% + 1px);
+ background-size: 30%;
+ // sass-lint:disable-block quotes space-after-comma
+ background-image: url(lightOrDarkTheme("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%23F5F7FA' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%23E6EBF2' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E","data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%2318191E' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%2315161B' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E"));
}
-.visNewVisDialog__description {
- width: $euiSizeXL * 10;
+.visNewVisSearchDialog {
+ min-width: $modalWidth;
+ min-height: $modalHeight;
}
-.visNewVisDialog__type:disabled {
- opacity: 0.2;
- pointer-events: none;
+.visNewVisDialog__toolsCard {
+ background-color: transparent;
}
-.visNewVisDialog__typeImage {
- @include size($euiSizeL);
+.visNewVisDialog__groupsCard {
+ background-color: transparent;
+ box-shadow: none;
}
-@include euiBreakpoint('xs', 's') {
- .visNewVisDialog {
- background-image: none;
- }
-
- .visNewVisDialog__typesWrapper {
- max-width: none;
- }
-
- .visNewVisDialog__types {
- justify-content: flex-start;
- }
+.visNewVisDialog__groupsCardWrapper {
+ position: relative;
- .visNewVisDialog__description {
- display: none;
+ .visNewVisDialog__groupsCardLink {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: - $euiSizeM;
}
-}
-@include internetExplorerOnly {
- .visNewVisDialog {
- width: 820px;
+ // overrides EuiBetaBadge specificity
+ .visNewVisDialog__groupsCardBetaBadge {
+ background: $euiColorEmptyShade;
+ cursor: pointer;
}
-
- .visNewVisDialog__body {
- flex-basis: 800px;
- }
-}
+}
\ No newline at end of file
diff --git a/src/plugins/visualizations/public/wizard/dialog_navigation.tsx b/src/plugins/visualizations/public/wizard/dialog_navigation.tsx
new file mode 100644
index 0000000000000..aa8e4c5580f52
--- /dev/null
+++ b/src/plugins/visualizations/public/wizard/dialog_navigation.tsx
@@ -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.
+ */
+
+import { EuiLink, EuiIcon, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+
+interface DialogNavigationProps {
+ goBack: () => void;
+}
+
+function DialogNavigation(props: DialogNavigationProps) {
+ return (
+ <>
+
+
+
+
+
+
+ {i18n.translate('visualizations.newVisWizard.goBackLink', {
+ defaultMessage: 'Go back',
+ })}
+
+
+
+
+ >
+ );
+}
+
+export { DialogNavigation };
diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.scss b/src/plugins/visualizations/public/wizard/group_selection/group_selection.scss
new file mode 100644
index 0000000000000..ce46b872a97ee
--- /dev/null
+++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.scss
@@ -0,0 +1,30 @@
+.visNewVisDialogGroupSelection__body {
+ // override EUI specificity
+ .euiModalBody__overflow {
+ padding: 0 !important; // sass-lint:disable-line no-important
+ }
+}
+
+.visNewVisDialogGroupSelection__visGroups {
+ padding: $euiSizeS $euiSizeXL 0;
+}
+
+.visNewVisDialogGroupSelection__footer {
+ @include euiBreakpoint('xs', 's') {
+ background: $euiColorEmptyShade;
+ }
+
+ padding: 0 $euiSizeXL $euiSizeL;
+ background: $euiColorLightestShade;
+}
+
+.visNewVisDialogGroupSelection__footerDescriptionList {
+ @include euiBreakpoint('xs', 's') {
+ padding-top: $euiSizeL;
+ }
+}
+
+.visNewVisDialogGroupSelection__footerDescriptionListTitle {
+ // override EUI specificity
+ width: auto !important; // sass-lint:disable-line no-important
+}
diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx
new file mode 100644
index 0000000000000..b357f42bbae8b
--- /dev/null
+++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx
@@ -0,0 +1,321 @@
+/*
+ * 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 { mountWithIntl } from 'test_utils/enzyme_helpers';
+import { TypesStart, VisType, VisGroups } from '../../vis_types';
+import { GroupSelection } from './group_selection';
+import { DocLinksStart } from '../../../../../core/public';
+
+describe('GroupSelection', () => {
+ const defaultVisTypeParams = {
+ hidden: false,
+ visualization: class Controller {
+ public render = jest.fn();
+ public destroy = jest.fn();
+ },
+ requiresSearch: false,
+ requestHandler: 'none',
+ responseHandler: 'none',
+ };
+ const _visTypes = [
+ {
+ name: 'vis1',
+ title: 'Vis Type 1',
+ description: 'Vis Type 1',
+ stage: 'production',
+ group: VisGroups.PROMOTED,
+ ...defaultVisTypeParams,
+ },
+ {
+ name: 'vis2',
+ title: 'Vis Type 2',
+ description: 'Vis Type 2',
+ group: VisGroups.PROMOTED,
+ stage: 'production',
+ ...defaultVisTypeParams,
+ },
+ {
+ name: 'visWithAliasUrl',
+ title: 'Vis with alias Url',
+ aliasApp: 'aliasApp',
+ aliasPath: '#/aliasApp',
+ disabled: true,
+ promoTooltip: {
+ description: 'Learn More',
+ link: '#/anotherUrl',
+ },
+ description: 'Vis with alias Url',
+ stage: 'production',
+ group: VisGroups.PROMOTED,
+ },
+ {
+ name: 'visAliasWithPromotion',
+ title: 'Vis alias with promotion',
+ description: 'Vis alias with promotion',
+ stage: 'production',
+ group: VisGroups.PROMOTED,
+ aliasApp: 'anotherApp',
+ aliasPath: '#/anotherUrl',
+ promotion: true,
+ } as unknown,
+ ] as VisType[];
+
+ const visTypesRegistry = (visTypes: VisType[]): TypesStart => {
+ return {
+ get(id: string): VisType {
+ return (visTypes.find((vis) => vis.name === id) as unknown) as VisType;
+ },
+ all: () => {
+ return (visTypes as unknown) as VisType[];
+ },
+ getAliases: () => [],
+ unRegisterAlias: () => [],
+ getByGroup: (group: VisGroups) => {
+ return (visTypes.filter((type) => {
+ return type.group === group;
+ }) as unknown) as VisType[];
+ },
+ };
+ };
+
+ const docLinks = {
+ links: {
+ dashboard: {
+ guide: 'test',
+ },
+ },
+ };
+
+ beforeAll(() => {
+ Object.defineProperty(window, 'location', {
+ value: {
+ assign: jest.fn(),
+ },
+ });
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should render the header title', () => {
+ const wrapper = mountWithIntl(
+
+ );
+ expect(wrapper.find('[data-test-subj="groupModalHeader"]').at(0).text()).toBe(
+ 'New visualization'
+ );
+ });
+
+ it('should not render the aggBased group card if no aggBased visualization is registered', () => {
+ const wrapper = mountWithIntl(
+
+ );
+ expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(false);
+ });
+
+ it('should render the aggBased group card if an aggBased group vis is registered', () => {
+ const aggBasedVisType = {
+ name: 'visWithSearch',
+ title: 'Vis with search',
+ group: VisGroups.AGGBASED,
+ stage: 'production',
+ ...defaultVisTypeParams,
+ };
+ const wrapper = mountWithIntl(
+
+ );
+ expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(true);
+ });
+
+ it('should not render the tools group card if no tools visualization is registered', () => {
+ const wrapper = mountWithIntl(
+
+ );
+ expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(false);
+ });
+
+ it('should render the tools group card if a tools group vis is registered', () => {
+ const toolsVisType = {
+ name: 'vis3',
+ title: 'Vis3',
+ stage: 'production',
+ group: VisGroups.TOOLS,
+ ...defaultVisTypeParams,
+ };
+ const wrapper = mountWithIntl(
+
+ );
+ expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(true);
+ });
+
+ it('should call the toggleGroups if the aggBased group card is clicked', () => {
+ const toggleGroups = jest.fn();
+ const aggBasedVisType = {
+ name: 'visWithSearch',
+ title: 'Vis with search',
+ group: VisGroups.AGGBASED,
+ stage: 'production',
+ ...defaultVisTypeParams,
+ };
+ const wrapper = mountWithIntl(
+
+ );
+ const aggBasedGroupCard = wrapper.find('[data-test-subj="visGroupAggBasedExploreLink"]').at(0);
+ aggBasedGroupCard.simulate('click');
+ expect(toggleGroups).toHaveBeenCalled();
+ });
+
+ it('should sort promoted visualizations first', () => {
+ const wrapper = mountWithIntl(
+
+ );
+
+ const cards = [
+ ...new Set(
+ wrapper.find('[data-test-subj^="visType-"]').map((card) => card.prop('data-test-subj'))
+ ),
+ ];
+
+ expect(cards).toEqual([
+ 'visType-visAliasWithPromotion',
+ 'visType-vis1',
+ 'visType-vis2',
+ 'visType-visWithAliasUrl',
+ ]);
+ });
+
+ it('should render disabled aliases with a disabled class', () => {
+ const wrapper = mountWithIntl(
+
+ );
+ expect(wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').exists()).toBe(true);
+ expect(
+ wrapper
+ .find('[data-test-subj="visType-visWithAliasUrl"]')
+ .at(1)
+ .hasClass('euiCard-isDisabled')
+ ).toBe(true);
+ });
+
+ it('should render a basic badge with link for disabled aliases with promoTooltip', () => {
+ const wrapper = mountWithIntl(
+
+ );
+ expect(wrapper.find('[data-test-subj="visTypeBadge"]').exists()).toBe(true);
+ expect(wrapper.find('[data-test-subj="visTypeBadge"]').at(0).prop('tooltipContent')).toBe(
+ 'Learn More'
+ );
+ });
+
+ it('should not show tools experimental visualizations if showExperimentalis false', () => {
+ const expVis = {
+ name: 'visExp',
+ title: 'Experimental Vis',
+ group: VisGroups.TOOLS,
+ stage: 'experimental',
+ ...defaultVisTypeParams,
+ };
+ const wrapper = mountWithIntl(
+
+ );
+ expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false);
+ });
+
+ it('should show tools experimental visualizations if showExperimental is true', () => {
+ const expVis = {
+ name: 'visExp',
+ title: 'Experimental Vis',
+ group: VisGroups.TOOLS,
+ stage: 'experimental',
+ ...defaultVisTypeParams,
+ };
+ const wrapper = mountWithIntl(
+
+ );
+ expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true);
+ });
+});
diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx
new file mode 100644
index 0000000000000..8520b84cc42ad
--- /dev/null
+++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx
@@ -0,0 +1,294 @@
+/*
+ * 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 React, { useCallback, useMemo } from 'react';
+import { orderBy } from 'lodash';
+import {
+ EuiFlexGroup,
+ EuiFlexGrid,
+ EuiFlexItem,
+ EuiCard,
+ EuiIcon,
+ EuiModalHeader,
+ EuiModalBody,
+ EuiModalHeaderTitle,
+ EuiLink,
+ EuiText,
+ EuiSpacer,
+ EuiBetaBadge,
+ EuiTitle,
+ EuiDescriptionListTitle,
+ EuiDescriptionListDescription,
+ EuiDescriptionList,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { DocLinksStart } from '../../../../../core/public';
+import { VisTypeAlias } from '../../vis_types/vis_type_alias_registry';
+import type { VisType, TypesStart } from '../../vis_types';
+import { VisGroups } from '../../vis_types';
+import './group_selection.scss';
+
+interface GroupSelectionProps {
+ onVisTypeSelected: (visType: VisType | VisTypeAlias) => void;
+ visTypesRegistry: TypesStart;
+ docLinks: DocLinksStart;
+ toggleGroups: (flag: boolean) => void;
+ showExperimental: boolean;
+}
+
+interface VisCardProps {
+ onVisTypeSelected: (visType: VisType | VisTypeAlias) => void;
+ visType: VisType | VisTypeAlias;
+ showExperimental?: boolean | undefined;
+}
+
+function isVisTypeAlias(type: VisType | VisTypeAlias): type is VisTypeAlias {
+ return 'aliasPath' in type;
+}
+
+function GroupSelection(props: GroupSelectionProps) {
+ const visualizeGuideLink = props.docLinks.links.dashboard.guide;
+ const promotedVisGroups = useMemo(
+ () =>
+ orderBy(
+ [
+ ...props.visTypesRegistry.getAliases(),
+ ...props.visTypesRegistry.getByGroup(VisGroups.PROMOTED),
+ ],
+ ['promotion', 'title'],
+ ['asc', 'asc']
+ ),
+ [props.visTypesRegistry]
+ );
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ {promotedVisGroups.map((visType) => (
+
+ ))}
+
+
+
+
+
+
+ {props.visTypesRegistry.getByGroup(VisGroups.AGGBASED).length > 0 && (
+
+ props.toggleGroups(false)}
+ title={
+
+ {i18n.translate('visualizations.newVisWizard.aggBasedGroupTitle', {
+ defaultMessage: 'Aggregation based',
+ })}
+
+ }
+ data-test-subj="visGroup-aggbased"
+ description={i18n.translate(
+ 'visualizations.newVisWizard.aggBasedGroupDescription',
+ {
+ defaultMessage:
+ 'Use our classic visualize library to create charts based on aggregations.',
+ }
+ )}
+ icon={ }
+ className="visNewVisDialog__groupsCard"
+ >
+ props.toggleGroups(false)}
+ >
+
+ {i18n.translate('visualizations.newVisWizard.exploreOptionLinkText', {
+ defaultMessage: 'Explore options',
+ })}{' '}
+
+
+
+
+
+ )}
+ {props.visTypesRegistry.getByGroup(VisGroups.TOOLS).length > 0 && (
+
+
+
+
+ {i18n.translate('visualizations.newVisWizard.toolsGroupTitle', {
+ defaultMessage: 'Tools',
+ })}
+
+
+
+
+ {props.visTypesRegistry.getByGroup(VisGroups.TOOLS).map((visType) => (
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+const VisGroup = ({ visType, onVisTypeSelected }: VisCardProps) => {
+ const onClick = useCallback(() => {
+ onVisTypeSelected(visType);
+ }, [onVisTypeSelected, visType]);
+ const shouldDisableCard = isVisTypeAlias(visType) && visType.disabled;
+ const betaBadgeContent =
+ shouldDisableCard && 'promoTooltip' in visType ? (
+
+
+
+ ) : undefined;
+ return (
+
+ {betaBadgeContent}
+
+ {'titleInWizard' in visType && visType.titleInWizard
+ ? visType.titleInWizard
+ : visType.title}
+
+ }
+ onClick={onClick}
+ isDisabled={shouldDisableCard}
+ data-test-subj={`visType-${visType.name}`}
+ data-vis-stage={!('aliasPath' in visType) ? visType.stage : 'alias'}
+ aria-label={`visType-${visType.name}`}
+ description={
+ <>
+ {visType.description || ''}
+ {visType.note || ''}
+ >
+ }
+ layout="horizontal"
+ icon={ }
+ className="visNewVisDialog__groupsCard"
+ />
+
+ );
+};
+
+const ToolsGroup = ({ visType, onVisTypeSelected, showExperimental }: VisCardProps) => {
+ const onClick = useCallback(() => {
+ onVisTypeSelected(visType);
+ }, [onVisTypeSelected, visType]);
+ // hide the experimental visualization if lab mode is not enabled
+ if (!showExperimental && visType.stage === 'experimental') {
+ return null;
+ }
+ return (
+
+
+
+
+
+
+
+
+ {'titleInWizard' in visType && visType.titleInWizard
+ ? visType.titleInWizard
+ : visType.title}
+
+
+ {visType.stage === 'experimental' && (
+
+
+
+ )}
+
+
+ {visType.description}
+
+
+
+ );
+};
+
+export { GroupSelection };
diff --git a/src/plugins/visualizations/public/wizard/group_selection/index.ts b/src/plugins/visualizations/public/wizard/group_selection/index.ts
new file mode 100644
index 0000000000000..80bd3dda7ac40
--- /dev/null
+++ b/src/plugins/visualizations/public/wizard/group_selection/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 { GroupSelection } from './group_selection';
diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx
index 51bcfed201687..eea364b754e31 100644
--- a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx
+++ b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx
@@ -19,9 +19,9 @@
import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
-import { TypesStart, VisType } from '../vis_types';
-import { NewVisModal } from './new_vis_modal';
-import { ApplicationStart, SavedObjectsStart } from '../../../../core/public';
+import { TypesStart, VisType, VisGroups } from '../vis_types';
+import NewVisModal from './new_vis_modal';
+import { ApplicationStart, SavedObjectsStart, DocLinksStart } from '../../../../core/public';
import { embeddablePluginMock } from '../../../embeddable/public/mocks';
describe('NewVisModal', () => {
@@ -36,31 +36,41 @@ describe('NewVisModal', () => {
responseHandler: 'none',
};
const _visTypes = [
- { name: 'vis', title: 'Vis Type 1', stage: 'production', ...defaultVisTypeParams },
- { name: 'visExp', title: 'Experimental Vis', stage: 'experimental', ...defaultVisTypeParams },
{
- name: 'visWithSearch',
- title: 'Vis with search',
+ name: 'vis',
+ title: 'Vis Type 1',
+ stage: 'production',
+ group: VisGroups.PROMOTED,
+ ...defaultVisTypeParams,
+ },
+ {
+ name: 'vis2',
+ title: 'Vis Type 2',
+ group: VisGroups.PROMOTED,
stage: 'production',
...defaultVisTypeParams,
},
+ {
+ name: 'vis3',
+ title: 'Vis3',
+ stage: 'production',
+ group: VisGroups.TOOLS,
+ ...defaultVisTypeParams,
+ },
{
name: 'visWithAliasUrl',
title: 'Vis with alias Url',
stage: 'production',
+ group: VisGroups.PROMOTED,
aliasApp: 'otherApp',
aliasPath: '#/aliasUrl',
},
{
- name: 'visAliasWithPromotion',
- title: 'Vis alias with promotion',
+ name: 'visWithSearch',
+ title: 'Vis with search',
+ group: VisGroups.AGGBASED,
stage: 'production',
- aliasApp: 'anotherApp',
- aliasPath: '#/anotherUrl',
- promotion: {
- description: 'promotion description',
- buttonText: 'another app',
- },
+ ...defaultVisTypeParams,
},
];
const visTypes: TypesStart = {
@@ -71,10 +81,23 @@ describe('NewVisModal', () => {
return (_visTypes as unknown) as VisType[];
},
getAliases: () => [],
+ unRegisterAlias: () => [],
+ getByGroup: (group: VisGroups) => {
+ return (_visTypes.filter((type) => {
+ return type.group === group;
+ }) as unknown) as VisType[];
+ },
};
const addBasePath = (url: string) => `testbasepath${url}`;
const settingsGet = jest.fn();
const uiSettings: any = { get: settingsGet };
+ const docLinks = {
+ links: {
+ dashboard: {
+ guide: 'test',
+ },
+ },
+ };
beforeAll(() => {
Object.defineProperty(window, 'location', {
@@ -88,7 +111,7 @@ describe('NewVisModal', () => {
jest.clearAllMocks();
});
- it('should render as expected', () => {
+ it('should show the aggbased group but not the visualization assigned to this group', () => {
const wrapper = mountWithIntl(
{
addBasePath={addBasePath}
uiSettings={uiSettings}
application={{} as ApplicationStart}
+ docLinks={docLinks as DocLinksStart}
savedObjects={{} as SavedObjectsStart}
/>
);
- expect(wrapper).toMatchSnapshot();
+ expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(true);
+ expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(false);
});
- it('should show a button for regular visualizations', () => {
+ it('should show the tools group', () => {
const wrapper = mountWithIntl(
{
addBasePath={addBasePath}
uiSettings={uiSettings}
application={{} as ApplicationStart}
+ docLinks={docLinks as DocLinksStart}
savedObjects={{} as SavedObjectsStart}
/>
);
- expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true);
+ expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(true);
});
- it('should sort promoted visualizations first', () => {
+ it('should display the visualizations of the other group', () => {
const wrapper = mountWithIntl(
{
addBasePath={addBasePath}
uiSettings={uiSettings}
application={{} as ApplicationStart}
+ docLinks={docLinks as DocLinksStart}
savedObjects={{} as SavedObjectsStart}
/>
);
- expect(
- wrapper
- .find('button[data-test-subj^="visType-"]')
- .map((button) => button.prop('data-test-subj'))
- ).toEqual([
- 'visType-visAliasWithPromotion',
- 'visType-vis',
- 'visType-visWithAliasUrl',
- 'visType-visWithSearch',
- ]);
+ expect(wrapper.find('[data-test-subj="visType-vis2"]').exists()).toBe(true);
});
describe('open editor', () => {
@@ -152,11 +170,12 @@ describe('NewVisModal', () => {
addBasePath={addBasePath}
uiSettings={uiSettings}
application={{} as ApplicationStart}
+ docLinks={docLinks as DocLinksStart}
savedObjects={{} as SavedObjectsStart}
/>
);
- const visButton = wrapper.find('button[data-test-subj="visType-vis"]');
- visButton.simulate('click');
+ const visCard = wrapper.find('[data-test-subj="visType-vis"]').at(0);
+ visCard.simulate('click');
expect(window.location.assign).toBeCalledWith('testbasepath/app/visualize#/create?type=vis');
});
@@ -170,11 +189,12 @@ describe('NewVisModal', () => {
addBasePath={addBasePath}
uiSettings={uiSettings}
application={{} as ApplicationStart}
+ docLinks={docLinks as DocLinksStart}
savedObjects={{} as SavedObjectsStart}
/>
);
- const visButton = wrapper.find('button[data-test-subj="visType-vis"]');
- visButton.simulate('click');
+ const visCard = wrapper.find('[data-test-subj="visType-vis"]').at(0);
+ visCard.simulate('click');
expect(window.location.assign).toBeCalledWith(
'testbasepath/app/visualize#/create?type=vis&foo=true&bar=42'
);
@@ -194,12 +214,13 @@ describe('NewVisModal', () => {
addBasePath={addBasePath}
uiSettings={uiSettings}
application={({ navigateToApp } as unknown) as ApplicationStart}
+ docLinks={docLinks as DocLinksStart}
stateTransfer={stateTransfer}
savedObjects={{} as SavedObjectsStart}
/>
);
- const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]');
- visButton.simulate('click');
+ const visCard = wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').at(0);
+ visCard.simulate('click');
expect(stateTransfer.navigateToEditor).toBeCalledWith('otherApp', {
path: '#/aliasUrl',
state: { originatingApp: 'coolJestTestApp' },
@@ -219,17 +240,18 @@ describe('NewVisModal', () => {
addBasePath={addBasePath}
uiSettings={uiSettings}
application={({ navigateToApp } as unknown) as ApplicationStart}
+ docLinks={docLinks as DocLinksStart}
savedObjects={{} as SavedObjectsStart}
/>
);
- const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]');
- visButton.simulate('click');
+ const visCard = wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').at(0);
+ visCard.simulate('click');
expect(navigateToApp).toBeCalledWith('otherApp', { path: '#/aliasUrl' });
expect(onClose).toHaveBeenCalled();
});
});
- describe('filter for visualization types', () => {
+ describe('aggBased visualizations', () => {
it('should render as expected', () => {
const wrapper = mountWithIntl(
{
addBasePath={addBasePath}
uiSettings={uiSettings}
application={{} as ApplicationStart}
+ docLinks={docLinks as DocLinksStart}
savedObjects={{} as SavedObjectsStart}
/>
);
- const searchBox = wrapper.find('input[data-test-subj="filterVisType"]');
- searchBox.simulate('change', { target: { value: 'with' } });
- expect(wrapper).toMatchSnapshot();
- });
- });
-
- describe('experimental visualizations', () => {
- it('should not show experimental visualizations if visualize:enableLabs is false', () => {
- settingsGet.mockReturnValue(false);
- const wrapper = mountWithIntl(
- null}
- visTypesRegistry={visTypes}
- addBasePath={addBasePath}
- uiSettings={uiSettings}
- application={{} as ApplicationStart}
- savedObjects={{} as SavedObjectsStart}
- />
- );
- expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false);
- });
-
- it('should show experimental visualizations if visualize:enableLabs is true', () => {
- settingsGet.mockReturnValue(true);
- const wrapper = mountWithIntl(
- null}
- visTypesRegistry={visTypes}
- addBasePath={addBasePath}
- uiSettings={uiSettings}
- application={{} as ApplicationStart}
- savedObjects={{} as SavedObjectsStart}
- />
- );
- expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true);
+ const aggBasedGroupCard = wrapper
+ .find('[data-test-subj="visGroupAggBasedExploreLink"]')
+ .at(0);
+ aggBasedGroupCard.simulate('click');
+ expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(true);
});
});
});
diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx
index 4bedd3eb1c22a..fbd4e6ef80d5a 100644
--- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx
+++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx
@@ -23,13 +23,20 @@ import { EuiModal, EuiOverlayMask } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics';
-import { ApplicationStart, IUiSettingsClient, SavedObjectsStart } from '../../../../core/public';
+import {
+ ApplicationStart,
+ IUiSettingsClient,
+ SavedObjectsStart,
+ DocLinksStart,
+} from '../../../../core/public';
import { SearchSelection } from './search_selection';
-import { TypeSelection } from './type_selection';
-import { TypesStart, VisType, VisTypeAlias } from '../vis_types';
+import { GroupSelection } from './group_selection';
+import { AggBasedSelection } from './agg_based_selection';
+import type { TypesStart, VisType, VisTypeAlias } from '../vis_types';
import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public';
import { EmbeddableStateTransfer } from '../../../embeddable/public';
import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants';
+import './dialog.scss';
interface TypeSelectionProps {
isOpen: boolean;
@@ -38,6 +45,7 @@ interface TypeSelectionProps {
editorParams?: string[];
addBasePath: (path: string) => string;
uiSettings: IUiSettingsClient;
+ docLinks: DocLinksStart;
savedObjects: SavedObjectsStart;
usageCollection?: UsageCollectionSetup;
application: ApplicationStart;
@@ -48,6 +56,7 @@ interface TypeSelectionProps {
interface TypeSelectionState {
showSearchVisModal: boolean;
+ showGroups: boolean;
visType?: VisType;
}
@@ -72,6 +81,7 @@ class NewVisModal extends React.Component
@@ -101,19 +113,21 @@ class NewVisModal extends React.Component this.setState({ showSearchVisModal: false })}
/>
) : (
- this.setState({ showGroups: flag })}
/>
);
@@ -185,4 +199,6 @@ class NewVisModal extends React.Component void;
visType: VisType;
uiSettings: IUiSettingsClient;
savedObjects: SavedObjectsStart;
+ goBack: () => void;
}
export class SearchSelection extends React.Component {
@@ -54,6 +56,7 @@ export class SearchSelection extends React.Component {
+
import('./new_vis_modal'));
+
export interface ShowNewVisModalParams {
editorParams?: string[];
onClose?: () => void;
@@ -66,20 +68,29 @@ export function showNewVisModal({
document.body.appendChild(container);
const element = (
-
+
+
+
+ }
+ >
+
+
);
ReactDOM.render(element, container);
diff --git a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx b/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx
deleted file mode 100644
index a5b6e8039ba6d..0000000000000
--- a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx
+++ /dev/null
@@ -1,73 +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 React from 'react';
-import { shallowWithIntl } from 'test_utils/enzyme_helpers';
-import { NewVisHelp } from './new_vis_help';
-
-describe('NewVisHelp', () => {
- it('should render as expected', () => {
- expect(
- shallowWithIntl(
- {}}
- />
- )
- ).toMatchInlineSnapshot(`
-
-
-
-
-
-
- Look at this fancy new thing!!!
-
-
-
- Do it now!
-
-
- `);
- });
-});
diff --git a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx b/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx
deleted file mode 100644
index 5b226a889408f..0000000000000
--- a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx
+++ /dev/null
@@ -1,57 +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 { FormattedMessage } from '@kbn/i18n/react';
-import React, { Fragment } from 'react';
-import { EuiText, EuiButton } from '@elastic/eui';
-import { VisTypeAlias } from '../../vis_types';
-
-interface Props {
- promotedTypes: VisTypeAlias[];
- onPromotionClicked: (visType: VisTypeAlias) => void;
-}
-
-export function NewVisHelp(props: Props) {
- return (
-
-
-
-
- {props.promotedTypes.map((t) => (
-
-
- {t.promotion!.description}
-
- props.onPromotionClicked(t)}
- fill
- size="s"
- iconType="popout"
- iconSide="right"
- >
- {t.promotion!.buttonText}
-
-
- ))}
-
- );
-}
diff --git a/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx b/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx
deleted file mode 100644
index 8c086ed132ae4..0000000000000
--- a/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx
+++ /dev/null
@@ -1,289 +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 { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { orderBy } from 'lodash';
-import React, { ChangeEvent } from 'react';
-
-import {
- EuiFieldSearch,
- EuiFlexGroup,
- EuiFlexItem,
- EuiKeyPadMenu,
- EuiKeyPadMenuItem,
- EuiModalHeader,
- EuiModalHeaderTitle,
- EuiScreenReaderOnly,
- EuiSpacer,
- EuiTitle,
-} from '@elastic/eui';
-
-import { memoizeLast } from '../../legacy/memoize';
-import { VisTypeAlias } from '../../vis_types/vis_type_alias_registry';
-import { NewVisHelp } from './new_vis_help';
-import { VisHelpText } from './vis_help_text';
-import { VisTypeIcon } from './vis_type_icon';
-import { VisType, TypesStart } from '../../vis_types';
-
-interface VisTypeListEntry {
- type: VisType | VisTypeAlias;
- highlighted: boolean;
-}
-
-interface TypeSelectionProps {
- addBasePath: (path: string) => string;
- onVisTypeSelected: (visType: VisType | VisTypeAlias) => void;
- visTypesRegistry: TypesStart;
- showExperimental: boolean;
-}
-
-interface HighlightedType {
- name: string;
- title: string;
- description?: string;
- highlightMsg?: string;
-}
-
-interface TypeSelectionState {
- highlightedType: HighlightedType | null;
- query: string;
-}
-
-function isVisTypeAlias(type: VisType | VisTypeAlias): type is VisTypeAlias {
- return 'aliasPath' in type;
-}
-
-class TypeSelection extends React.Component {
- public state: TypeSelectionState = {
- highlightedType: null,
- query: '',
- };
-
- private readonly getFilteredVisTypes = memoizeLast(this.filteredVisTypes);
-
- public render() {
- const { query, highlightedType } = this.state;
- const visTypes = this.getFilteredVisTypes(this.props.visTypesRegistry, query);
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {query && (
- type.highlighted).length,
- }}
- />
- )}
-
-
-
- {visTypes.map(this.renderVisType)}
-
-
-
-
-
- {highlightedType ? (
-
- ) : (
-
-
-
-
-
-
-
- t.type)
- .filter((t): t is VisTypeAlias => isVisTypeAlias(t) && Boolean(t.promotion))}
- onPromotionClicked={this.props.onVisTypeSelected}
- />
-
- )}
-
-
-
-
- );
- }
-
- private filteredVisTypes(visTypes: TypesStart, query: string): VisTypeListEntry[] {
- const types = visTypes.all().filter((type) => {
- // Filter out all lab visualizations if lab mode is not enabled
- if (!this.props.showExperimental && type.stage === 'experimental') {
- return false;
- }
-
- // Filter out hidden visualizations
- if (type.hidden) {
- return false;
- }
-
- return true;
- });
-
- const allTypes = [...types, ...visTypes.getAliases()];
-
- let entries: VisTypeListEntry[];
- if (!query) {
- entries = allTypes.map((type) => ({ type, highlighted: false }));
- } else {
- const q = query.toLowerCase();
- entries = allTypes.map((type) => {
- const matchesQuery =
- type.name.toLowerCase().includes(q) ||
- type.title.toLowerCase().includes(q) ||
- (typeof type.description === 'string' && type.description.toLowerCase().includes(q));
- return { type, highlighted: matchesQuery };
- });
- }
-
- return orderBy(
- entries,
- ['highlighted', 'type.promotion', 'type.title'],
- ['desc', 'asc', 'asc']
- );
- }
-
- private renderVisType = (visType: VisTypeListEntry) => {
- let stage = {};
- let highlightMsg;
- if (!isVisTypeAlias(visType.type) && visType.type.stage === 'experimental') {
- stage = {
- betaBadgeLabel: i18n.translate('visualizations.newVisWizard.experimentalTitle', {
- defaultMessage: 'Experimental',
- }),
- betaBadgeTooltipContent: i18n.translate('visualizations.newVisWizard.experimentalTooltip', {
- defaultMessage:
- 'This visualization might be changed or removed in a future release and is not subject to the support SLA.',
- }),
- };
- highlightMsg = i18n.translate('visualizations.newVisWizard.experimentalDescription', {
- defaultMessage:
- 'This visualization is experimental. The design and implementation are less mature than stable visualizations and might be subject to change.',
- });
- } else if (isVisTypeAlias(visType.type) && visType.type.stage === 'beta') {
- const aliasDescription = i18n.translate('visualizations.newVisWizard.betaDescription', {
- defaultMessage:
- 'This visualization is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features',
- });
- stage = {
- betaBadgeLabel: i18n.translate('visualizations.newVisWizard.betaTitle', {
- defaultMessage: 'Beta',
- }),
- betaBadgeTooltipContent: aliasDescription,
- };
- highlightMsg = aliasDescription;
- }
-
- const isDisabled = this.state.query !== '' && !visType.highlighted;
- const onClick = () => this.props.onVisTypeSelected(visType.type);
-
- const highlightedType: HighlightedType = {
- title: visType.type.title,
- name: visType.type.name,
- description: visType.type.description,
- highlightMsg,
- };
-
- return (
- {visType.type.title}}
- onClick={onClick}
- onFocus={() => this.setHighlightType(highlightedType)}
- onMouseEnter={() => this.setHighlightType(highlightedType)}
- onMouseLeave={() => this.setHighlightType(null)}
- onBlur={() => this.setHighlightType(null)}
- className="visNewVisDialog__type"
- data-test-subj={`visType-${visType.type.name}`}
- data-vis-stage={!isVisTypeAlias(visType.type) ? visType.type.stage : 'alias'}
- disabled={isDisabled}
- aria-describedby={`visTypeDescription-${visType.type.name}`}
- {...stage}
- >
-
-
- );
- };
-
- private setHighlightType(highlightedType: HighlightedType | null) {
- this.setState({
- highlightedType,
- });
- }
-
- private onQueryChange = (ev: ChangeEvent) => {
- this.setState({
- query: ev.target.value,
- });
- };
-}
-
-export { TypeSelection };
diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js
index c2e36b4a669ff..e5da46644672b 100644
--- a/test/api_integration/apis/saved_objects/find.js
+++ b/test/api_integration/apis/saved_objects/find.js
@@ -334,6 +334,70 @@ export default function ({ getService }) {
});
});
+ describe('searching for special characters', () => {
+ before(() => esArchiver.load('saved_objects/find_edgecases'));
+ after(() => esArchiver.unload('saved_objects/find_edgecases'));
+
+ it('can search for objects with dashes', async () =>
+ await supertest
+ .get('/api/saved_objects/_find')
+ .query({
+ type: 'visualization',
+ search_fields: 'title',
+ search: 'my-vis*',
+ })
+ .expect(200)
+ .then((resp) => {
+ const savedObjects = resp.body.saved_objects;
+ expect(savedObjects.map((so) => so.attributes.title)).to.eql(['my-visualization']);
+ }));
+
+ it('can search with the prefix search character just after a special one', async () =>
+ await supertest
+ .get('/api/saved_objects/_find')
+ .query({
+ type: 'visualization',
+ search_fields: 'title',
+ search: 'my-*',
+ })
+ .expect(200)
+ .then((resp) => {
+ const savedObjects = resp.body.saved_objects;
+ expect(savedObjects.map((so) => so.attributes.title)).to.eql(['my-visualization']);
+ }));
+
+ it('can search for objects with asterisk', async () =>
+ await supertest
+ .get('/api/saved_objects/_find')
+ .query({
+ type: 'visualization',
+ search_fields: 'title',
+ search: 'some*vi*',
+ })
+ .expect(200)
+ .then((resp) => {
+ const savedObjects = resp.body.saved_objects;
+ expect(savedObjects.map((so) => so.attributes.title)).to.eql(['some*visualization']);
+ }));
+
+ it('can still search tokens by prefix', async () =>
+ await supertest
+ .get('/api/saved_objects/_find')
+ .query({
+ type: 'visualization',
+ search_fields: 'title',
+ search: 'visuali*',
+ })
+ .expect(200)
+ .then((resp) => {
+ const savedObjects = resp.body.saved_objects;
+ expect(savedObjects.map((so) => so.attributes.title)).to.eql([
+ 'my-visualization',
+ 'some*visualization',
+ ]);
+ }));
+ });
+
describe('without kibana index', () => {
before(
async () =>
diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json
new file mode 100644
index 0000000000000..0c8b35fd3f499
--- /dev/null
+++ b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json
@@ -0,0 +1,93 @@
+{
+ "type": "doc",
+ "value": {
+ "index": ".kibana",
+ "id": "visualization:title-with-dash",
+ "source": {
+ "type": "visualization",
+ "updated_at": "2017-09-21T18:51:23.794Z",
+ "visualization": {
+ "title": "my-visualization",
+ "visState": "{}",
+ "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}",
+ "description": "",
+ "version": 1,
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
+ }
+ },
+ "references": []
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "index": ".kibana",
+ "id": "visualization:title-with-asterisk",
+ "source": {
+ "type": "visualization",
+ "updated_at": "2017-09-21T18:51:23.794Z",
+ "visualization": {
+ "title": "some*visualization",
+ "visState": "{}",
+ "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}",
+ "description": "",
+ "version": 1,
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
+ }
+ },
+ "references": []
+ }
+ }
+}
+
+
+{
+ "type": "doc",
+ "value": {
+ "index": ".kibana",
+ "id": "visualization:noise-1",
+ "source": {
+ "type": "visualization",
+ "updated_at": "2017-09-21T18:51:23.794Z",
+ "visualization": {
+ "title": "Just some noise in the dataset",
+ "visState": "{}",
+ "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}",
+ "description": "",
+ "version": 1,
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
+ }
+ },
+ "references": []
+ }
+ }
+}
+
+{
+ "type": "doc",
+ "value": {
+ "index": ".kibana",
+ "id": "visualization:noise-2",
+ "source": {
+ "type": "visualization",
+ "updated_at": "2017-09-21T18:51:23.794Z",
+ "visualization": {
+ "title": "Just some noise in the dataset",
+ "visState": "{}",
+ "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}",
+ "description": "",
+ "version": 1,
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
+ }
+ },
+ "references": []
+ }
+ }
+}
+
diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json
new file mode 100644
index 0000000000000..e601c43431437
--- /dev/null
+++ b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json
@@ -0,0 +1,267 @@
+{
+ "type": "index",
+ "value": {
+ "index": ".kibana",
+ "settings": {
+ "index": {
+ "number_of_shards": "1",
+ "number_of_replicas": "1"
+ }
+ },
+ "mappings": {
+ "dynamic": "strict",
+ "properties": {
+ "config": {
+ "dynamic": "true",
+ "properties": {
+ "buildNum": {
+ "type": "keyword"
+ },
+ "defaultIndex": {
+ "type": "text",
+ "fields": {
+ "keyword": {
+ "type": "keyword",
+ "ignore_above": 256
+ }
+ }
+ }
+ }
+ },
+ "dashboard": {
+ "properties": {
+ "description": {
+ "type": "text"
+ },
+ "hits": {
+ "type": "integer"
+ },
+ "kibanaSavedObjectMeta": {
+ "properties": {
+ "searchSourceJSON": {
+ "type": "text"
+ }
+ }
+ },
+ "optionsJSON": {
+ "type": "text"
+ },
+ "panelsJSON": {
+ "type": "text"
+ },
+ "refreshInterval": {
+ "properties": {
+ "display": {
+ "type": "keyword"
+ },
+ "pause": {
+ "type": "boolean"
+ },
+ "section": {
+ "type": "integer"
+ },
+ "value": {
+ "type": "integer"
+ }
+ }
+ },
+ "timeFrom": {
+ "type": "keyword"
+ },
+ "timeRestore": {
+ "type": "boolean"
+ },
+ "timeTo": {
+ "type": "keyword"
+ },
+ "title": {
+ "type": "text"
+ },
+ "uiStateJSON": {
+ "type": "text"
+ },
+ "version": {
+ "type": "integer"
+ }
+ }
+ },
+ "index-pattern": {
+ "properties": {
+ "fieldFormatMap": {
+ "type": "text"
+ },
+ "fields": {
+ "type": "text"
+ },
+ "intervalName": {
+ "type": "keyword"
+ },
+ "notExpandable": {
+ "type": "boolean"
+ },
+ "sourceFilters": {
+ "type": "text"
+ },
+ "timeFieldName": {
+ "type": "keyword"
+ },
+ "title": {
+ "type": "text"
+ }
+ }
+ },
+ "search": {
+ "properties": {
+ "columns": {
+ "type": "keyword"
+ },
+ "description": {
+ "type": "text"
+ },
+ "hits": {
+ "type": "integer"
+ },
+ "kibanaSavedObjectMeta": {
+ "properties": {
+ "searchSourceJSON": {
+ "type": "text"
+ }
+ }
+ },
+ "sort": {
+ "type": "keyword"
+ },
+ "title": {
+ "type": "text"
+ },
+ "version": {
+ "type": "integer"
+ }
+ }
+ },
+ "server": {
+ "properties": {
+ "uuid": {
+ "type": "keyword"
+ }
+ }
+ },
+ "timelion-sheet": {
+ "properties": {
+ "description": {
+ "type": "text"
+ },
+ "hits": {
+ "type": "integer"
+ },
+ "kibanaSavedObjectMeta": {
+ "properties": {
+ "searchSourceJSON": {
+ "type": "text"
+ }
+ }
+ },
+ "timelion_chart_height": {
+ "type": "integer"
+ },
+ "timelion_columns": {
+ "type": "integer"
+ },
+ "timelion_interval": {
+ "type": "keyword"
+ },
+ "timelion_other_interval": {
+ "type": "keyword"
+ },
+ "timelion_rows": {
+ "type": "integer"
+ },
+ "timelion_sheet": {
+ "type": "text"
+ },
+ "title": {
+ "type": "text"
+ },
+ "version": {
+ "type": "integer"
+ }
+ }
+ },
+ "namespace": {
+ "type": "keyword"
+ },
+ "references": {
+ "properties": {
+ "id": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ }
+ },
+ "type": "nested"
+ },
+ "type": {
+ "type": "keyword"
+ },
+ "updated_at": {
+ "type": "date"
+ },
+ "url": {
+ "properties": {
+ "accessCount": {
+ "type": "long"
+ },
+ "accessDate": {
+ "type": "date"
+ },
+ "createDate": {
+ "type": "date"
+ },
+ "url": {
+ "type": "text",
+ "fields": {
+ "keyword": {
+ "type": "keyword",
+ "ignore_above": 2048
+ }
+ }
+ }
+ }
+ },
+ "visualization": {
+ "properties": {
+ "description": {
+ "type": "text"
+ },
+ "kibanaSavedObjectMeta": {
+ "properties": {
+ "searchSourceJSON": {
+ "type": "text"
+ }
+ }
+ },
+ "savedSearchId": {
+ "type": "keyword"
+ },
+ "title": {
+ "type": "text"
+ },
+ "uiStateJSON": {
+ "type": "text"
+ },
+ "version": {
+ "type": "integer"
+ },
+ "visState": {
+ "type": "text"
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.js b/test/functional/apps/dashboard/create_and_add_embeddables.js
index f5c2496a9a5aa..c315828c594fa 100644
--- a/test/functional/apps/dashboard/create_and_add_embeddables.js
+++ b/test/functional/apps/dashboard/create_and_add_embeddables.js
@@ -42,10 +42,11 @@ export default function ({ getService, getPageObjects }) {
});
describe('add new visualization link', () => {
- it('adds new visualiztion via the top nav link', async () => {
+ it('adds new visualization via the top nav link', async () => {
const originalPanelCount = await PageObjects.dashboard.getPanelCount();
await PageObjects.dashboard.switchToEditMode();
await dashboardAddPanel.clickCreateNewLink();
+ await PageObjects.visualize.clickAggBasedVisualizations();
await PageObjects.visualize.clickAreaChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.visualize.saveVisualizationExpectSuccess(
@@ -63,6 +64,7 @@ export default function ({ getService, getPageObjects }) {
const originalPanelCount = await PageObjects.dashboard.getPanelCount();
await dashboardAddPanel.ensureAddPanelIsShowing();
await dashboardAddPanel.clickAddNewEmbeddableLink('visualization');
+ await PageObjects.visualize.clickAggBasedVisualizations();
await PageObjects.visualize.clickAreaChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.visualize.saveVisualizationExpectSuccess(
diff --git a/test/functional/apps/dashboard/view_edit.js b/test/functional/apps/dashboard/view_edit.js
index 589a46b7e9d08..a6d81da131751 100644
--- a/test/functional/apps/dashboard/view_edit.js
+++ b/test/functional/apps/dashboard/view_edit.js
@@ -134,6 +134,7 @@ export default function ({ getService, getPageObjects }) {
await dashboardAddPanel.ensureAddPanelIsShowing();
await dashboardAddPanel.clickAddNewEmbeddableLink('visualization');
+ await PageObjects.visualize.clickAggBasedVisualizations();
await PageObjects.visualize.clickAreaChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.visualize.saveVisualizationExpectSuccess('new viz panel', {
diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js
index e597cc14654bc..3c9996ca44ff8 100644
--- a/test/functional/apps/discover/_discover.js
+++ b/test/functional/apps/discover/_discover.js
@@ -32,7 +32,8 @@ export default function ({ getService, getPageObjects }) {
defaultIndex: 'logstash-*',
};
- describe('discover test', function describeIndexTests() {
+ // Failing: See https://github.com/elastic/kibana/issues/82915
+ describe.skip('discover test', function describeIndexTests() {
before(async function () {
log.debug('load kibana index with default index pattern');
await esArchiver.load('discover');
diff --git a/test/functional/apps/getting_started/_shakespeare.js b/test/functional/apps/getting_started/_shakespeare.js
index 38ed3edc7deb5..a648dae99158d 100644
--- a/test/functional/apps/getting_started/_shakespeare.js
+++ b/test/functional/apps/getting_started/_shakespeare.js
@@ -74,7 +74,7 @@ export default function ({ getService, getPageObjects }) {
*/
it('should create initial vertical bar chart', async function () {
log.debug('create shakespeare vertical bar chart');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickVerticalBarChart();
await PageObjects.visualize.clickNewSearch('shakespeare');
await PageObjects.visChart.waitForVisualization();
diff --git a/test/functional/apps/visualize/_area_chart.js b/test/functional/apps/visualize/_area_chart.js
index 9ac2160a359da..b943e070e1768 100644
--- a/test/functional/apps/visualize/_area_chart.js
+++ b/test/functional/apps/visualize/_area_chart.js
@@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }) {
const initAreaChart = async () => {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickAreaChart');
await PageObjects.visualize.clickAreaChart();
log.debug('clickNewSearch');
@@ -390,7 +390,7 @@ export default function ({ getService, getPageObjects }) {
const toTime = 'Jan 1, 2020 @ 00:00:00.000';
it('should render a yearly area with 12 svg paths', async () => {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickAreaChart');
await PageObjects.visualize.clickAreaChart();
log.debug('clickNewSearch');
@@ -413,7 +413,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should render monthly areas with 168 svg paths', async () => {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickAreaChart');
await PageObjects.visualize.clickAreaChart();
log.debug('clickNewSearch');
diff --git a/test/functional/apps/visualize/_chart_types.ts b/test/functional/apps/visualize/_chart_types.ts
index ecb7e9630c2c6..4864fcbf3af09 100644
--- a/test/functional/apps/visualize/_chart_types.ts
+++ b/test/functional/apps/visualize/_chart_types.ts
@@ -33,10 +33,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visualize.navigateToNewVisualization();
});
- it('should show the correct chart types', async function () {
+ it('should show the promoted vis types for the first step', async function () {
+ const expectedChartTypes = ['Custom visualization', 'Lens', 'Maps', 'TSVB'];
+ log.debug('oss= ' + isOss);
+
+ // find all the chart types and make sure there all there
+ const chartTypes = (await PageObjects.visualize.getPromotedVisTypes()).sort();
+ log.debug('returned chart types = ' + chartTypes);
+ log.debug('expected chart types = ' + expectedChartTypes);
+ expect(chartTypes).to.eql(expectedChartTypes);
+ });
+
+ it('should show the correct agg based chart types', async function () {
+ await PageObjects.visualize.clickAggBasedVisualizations();
let expectedChartTypes = [
'Area',
- 'Controls',
'Coordinate Map',
'Data Table',
'Gauge',
@@ -44,18 +55,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
'Heat Map',
'Horizontal Bar',
'Line',
- 'Markdown',
'Metric',
'Pie',
'Region Map',
- 'TSVB',
'Tag Cloud',
'Timelion',
- 'Vega',
'Vertical Bar',
];
if (!isOss) {
- expectedChartTypes.push('Maps', 'Lens');
expectedChartTypes = _.remove(expectedChartTypes, function (n) {
return n !== 'Coordinate Map';
});
diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js
index a1389a69666ae..bd7511d373b90 100644
--- a/test/functional/apps/visualize/_data_table.js
+++ b/test/functional/apps/visualize/_data_table.js
@@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }) {
before(async function () {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickDataTable');
await PageObjects.visualize.clickDataTable();
log.debug('clickNewSearch');
@@ -107,7 +107,7 @@ export default function ({ getService, getPageObjects }) {
}
// load a plain table
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
@@ -152,7 +152,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should show correct data when using average pipeline aggregation', async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
@@ -167,7 +167,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should show correct data for a data table with date histogram', async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
@@ -189,7 +189,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should show correct data for a data table with date histogram', async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
@@ -224,7 +224,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should show correct data for a data table with top hits', async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
@@ -238,7 +238,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should show correct data for a data table with range agg', async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
@@ -257,7 +257,7 @@ export default function ({ getService, getPageObjects }) {
describe('otherBucket', () => {
before(async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
@@ -295,7 +295,7 @@ export default function ({ getService, getPageObjects }) {
describe('metricsOnAllLevels', () => {
before(async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
@@ -389,7 +389,7 @@ export default function ({ getService, getPageObjects }) {
describe('split tables', () => {
before(async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
diff --git a/test/functional/apps/visualize/_data_table_nontimeindex.js b/test/functional/apps/visualize/_data_table_nontimeindex.js
index fd06257a91ff4..f45e40970a57f 100644
--- a/test/functional/apps/visualize/_data_table_nontimeindex.js
+++ b/test/functional/apps/visualize/_data_table_nontimeindex.js
@@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }) {
before(async function () {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickDataTable');
await PageObjects.visualize.clickDataTable();
log.debug('clickNewSearch');
@@ -97,7 +97,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should show correct data when using average pipeline aggregation', async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch(
PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED
@@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }) {
describe('data table with date histogram', async () => {
before(async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch(
PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED
diff --git a/test/functional/apps/visualize/_data_table_notimeindex_filters.ts b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts
index 9fb6d793eb7d9..cdcf08d2514e5 100644
--- a/test/functional/apps/visualize/_data_table_notimeindex_filters.ts
+++ b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts
@@ -40,7 +40,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
before(async function () {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickDataTable');
await PageObjects.visualize.clickDataTable();
log.debug('clickNewSearch');
diff --git a/test/functional/apps/visualize/_embedding_chart.js b/test/functional/apps/visualize/_embedding_chart.js
index 5316bf51fd48c..773bd63d8892f 100644
--- a/test/functional/apps/visualize/_embedding_chart.js
+++ b/test/functional/apps/visualize/_embedding_chart.js
@@ -35,7 +35,7 @@ export default function ({ getService, getPageObjects }) {
describe('embedding', () => {
describe('a data table', () => {
before(async function () {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
diff --git a/test/functional/apps/visualize/_experimental_vis.js b/test/functional/apps/visualize/_experimental_vis.js
index e87a36434b231..c85d66eed12d2 100644
--- a/test/functional/apps/visualize/_experimental_vis.js
+++ b/test/functional/apps/visualize/_experimental_vis.js
@@ -27,7 +27,7 @@ export default ({ getService, getPageObjects }) => {
describe('experimental visualizations', () => {
beforeEach(async () => {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.waitForVisualizationSelectPage();
});
diff --git a/test/functional/apps/visualize/_gauge_chart.js b/test/functional/apps/visualize/_gauge_chart.js
index 0f870b1fb545f..06f5913aec814 100644
--- a/test/functional/apps/visualize/_gauge_chart.js
+++ b/test/functional/apps/visualize/_gauge_chart.js
@@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) {
describe('gauge chart', function indexPatternCreation() {
async function initGaugeVis() {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickGauge');
await PageObjects.visualize.clickGauge();
await PageObjects.visualize.clickNewSearch();
diff --git a/test/functional/apps/visualize/_heatmap_chart.js b/test/functional/apps/visualize/_heatmap_chart.js
index 98a0104629c85..e48173c290372 100644
--- a/test/functional/apps/visualize/_heatmap_chart.js
+++ b/test/functional/apps/visualize/_heatmap_chart.js
@@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) {
before(async function () {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickHeatmapChart');
await PageObjects.visualize.clickHeatmapChart();
await PageObjects.visualize.clickNewSearch();
diff --git a/test/functional/apps/visualize/_histogram_request_start.js b/test/functional/apps/visualize/_histogram_request_start.js
index f6440cfbd76ff..8489cffa805da 100644
--- a/test/functional/apps/visualize/_histogram_request_start.js
+++ b/test/functional/apps/visualize/_histogram_request_start.js
@@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }) {
describe('histogram agg onSearchRequestStart', function () {
before(async function () {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickDataTable');
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
diff --git a/test/functional/apps/visualize/_inspector.js b/test/functional/apps/visualize/_inspector.js
index 104184050e838..426e61d9636ad 100644
--- a/test/functional/apps/visualize/_inspector.js
+++ b/test/functional/apps/visualize/_inspector.js
@@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }) {
describe('inspector', function describeIndexTests() {
before(async function () {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickVerticalBarChart();
await PageObjects.visualize.clickNewSearch();
diff --git a/test/functional/apps/visualize/_line_chart.js b/test/functional/apps/visualize/_line_chart.js
index 8dfc4d352b133..8c6dbd3461a4f 100644
--- a/test/functional/apps/visualize/_line_chart.js
+++ b/test/functional/apps/visualize/_line_chart.js
@@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }) {
const initLineChart = async function () {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickLineChart');
await PageObjects.visualize.clickLineChart();
await PageObjects.visualize.clickNewSearch();
@@ -244,7 +244,7 @@ export default function ({ getService, getPageObjects }) {
describe('pipeline aggregations', () => {
before(async () => {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickLineChart');
await PageObjects.visualize.clickLineChart();
await PageObjects.visualize.clickNewSearch();
diff --git a/test/functional/apps/visualize/_linked_saved_searches.ts b/test/functional/apps/visualize/_linked_saved_searches.ts
index 4151e0e9b471c..a5a9c47d3d010 100644
--- a/test/functional/apps/visualize/_linked_saved_searches.ts
+++ b/test/functional/apps/visualize/_linked_saved_searches.ts
@@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('should create a visualization from a saved search', async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickSavedSearch(savedSearchName);
await PageObjects.timePicker.setDefaultAbsoluteRange();
diff --git a/test/functional/apps/visualize/_metric_chart.js b/test/functional/apps/visualize/_metric_chart.js
index e02dac11d9e98..483a0688d6aca 100644
--- a/test/functional/apps/visualize/_metric_chart.js
+++ b/test/functional/apps/visualize/_metric_chart.js
@@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) {
describe('metric chart', function () {
before(async function () {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickMetric');
await PageObjects.visualize.clickMetric();
await PageObjects.visualize.clickNewSearch();
diff --git a/test/functional/apps/visualize/_pie_chart.js b/test/functional/apps/visualize/_pie_chart.js
index b68a88c4f97f3..eef0f90005a43 100644
--- a/test/functional/apps/visualize/_pie_chart.js
+++ b/test/functional/apps/visualize/_pie_chart.js
@@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }) {
const vizName1 = 'Visualization PieChart';
before(async function () {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickPieChart');
await PageObjects.visualize.clickPieChart();
await PageObjects.visualize.clickNewSearch();
@@ -94,7 +94,7 @@ export default function ({ getService, getPageObjects }) {
it('should show other and missing bucket', async function () {
const expectedTableData = ['win 8', 'win xp', 'win 7', 'ios', 'Missing', 'Other'];
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickPieChart');
await PageObjects.visualize.clickPieChart();
await PageObjects.visualize.clickNewSearch();
@@ -292,7 +292,7 @@ export default function ({ getService, getPageObjects }) {
describe('empty time window', () => {
it('should show no data message when no data on selected timerange', async function () {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickPieChart');
await PageObjects.visualize.clickPieChart();
await PageObjects.visualize.clickNewSearch();
@@ -321,7 +321,7 @@ export default function ({ getService, getPageObjects }) {
describe('multi series slice', () => {
before(async () => {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickPieChart');
await PageObjects.visualize.clickPieChart();
await PageObjects.visualize.clickNewSearch();
@@ -443,7 +443,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should still showing pie chart when a subseries have zero data', async function () {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickPieChart');
await PageObjects.visualize.clickPieChart();
await PageObjects.visualize.clickNewSearch();
@@ -471,7 +471,7 @@ export default function ({ getService, getPageObjects }) {
describe('split chart', () => {
before(async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickPieChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js
index c88670ee8b741..a671cb82ab70f 100644
--- a/test/functional/apps/visualize/_point_series_options.js
+++ b/test/functional/apps/visualize/_point_series_options.js
@@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }) {
async function initChart() {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickLineChart');
await PageObjects.visualize.clickLineChart();
await PageObjects.visualize.clickNewSearch();
@@ -197,7 +197,7 @@ export default function ({ getService, getPageObjects }) {
describe('show values on chart', () => {
before(async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickVerticalBarChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
@@ -232,7 +232,7 @@ export default function ({ getService, getPageObjects }) {
const customLabel = 'myLabel';
const axisTitle = 'myTitle';
before(async function () {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickLineChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.visEditor.selectYAxisAggregation('Average', 'bytes', customLabel, 1);
diff --git a/test/functional/apps/visualize/_region_map.js b/test/functional/apps/visualize/_region_map.js
index f2fe1527c97c2..eee73d2fe76f9 100644
--- a/test/functional/apps/visualize/_region_map.js
+++ b/test/functional/apps/visualize/_region_map.js
@@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }) {
before(async function () {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickRegionMap');
await PageObjects.visualize.clickRegionMap();
await PageObjects.visualize.clickNewSearch();
diff --git a/test/functional/apps/visualize/_tag_cloud.js b/test/functional/apps/visualize/_tag_cloud.js
index b5c575edb8a0a..7b1671aaeb2f2 100644
--- a/test/functional/apps/visualize/_tag_cloud.js
+++ b/test/functional/apps/visualize/_tag_cloud.js
@@ -43,7 +43,7 @@ export default function ({ getService, getPageObjects }) {
before(async function () {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickTagCloud');
await PageObjects.visualize.clickTagCloud();
await PageObjects.visualize.clickNewSearch();
diff --git a/test/functional/apps/visualize/_tile_map.js b/test/functional/apps/visualize/_tile_map.js
index 56585ccd725d8..3ae72fe73a5fd 100644
--- a/test/functional/apps/visualize/_tile_map.js
+++ b/test/functional/apps/visualize/_tile_map.js
@@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }) {
await browser.setWindowSize(1280, 1000);
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickTileMap');
await PageObjects.visualize.clickTileMap();
await PageObjects.visualize.clickNewSearch();
@@ -61,7 +61,7 @@ export default function ({ getService, getPageObjects }) {
await browser.setWindowSize(1280, 1000);
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickTileMap');
await PageObjects.visualize.clickTileMap();
await PageObjects.visualize.clickNewSearch();
@@ -240,7 +240,7 @@ export default function ({ getService, getPageObjects }) {
await browser.setWindowSize(1280, 1000);
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickTileMap');
await PageObjects.visualize.clickTileMap();
await PageObjects.visualize.clickNewSearch();
diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js
index ff0423eb531da..5ecbf3f720ee6 100644
--- a/test/functional/apps/visualize/_vertical_bar_chart.js
+++ b/test/functional/apps/visualize/_vertical_bar_chart.js
@@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }) {
const initBarChart = async () => {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickVerticalBarChart');
await PageObjects.visualize.clickVerticalBarChart();
await PageObjects.visualize.clickNewSearch();
@@ -64,7 +64,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should not filter out first label after rotation of the chart', async function () {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickVerticalBarChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
@@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }) {
describe('bar charts range on x axis', () => {
it('should individual bars for each configured range', async function () {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickVerticalBarChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
diff --git a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js
index f95781c9bbb33..7fb123f2e304a 100644
--- a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js
+++ b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js
@@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) {
const initBarChart = async () => {
log.debug('navigateToApp visualize');
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
log.debug('clickVerticalBarChart');
await PageObjects.visualize.clickVerticalBarChart();
await PageObjects.visualize.clickNewSearch(
diff --git a/test/functional/page_objects/tile_map_page.ts b/test/functional/page_objects/tile_map_page.ts
index 609e6ebddd50a..7881c9b1f7155 100644
--- a/test/functional/page_objects/tile_map_page.ts
+++ b/test/functional/page_objects/tile_map_page.ts
@@ -50,12 +50,14 @@ export function TileMapPageProvider({ getService, getPageObjects }: FtrProviderC
await testSubjects.click('inspectorViewChooser');
await testSubjects.click('inspectorViewChooserRequests');
await testSubjects.click('inspectorRequestDetailRequest');
- return await testSubjects.getVisibleText('inspectorRequestBody');
+
+ return await inspector.getCodeEditorValue();
}
public async getMapBounds(): Promise {
const request = await this.getVisualizationRequest();
const requestObject = JSON.parse(request);
+
return requestObject.aggs.filter_agg.filter.geo_bounding_box['geo.coordinates'];
}
diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts
index 9619c81370cd8..b58acd0bd9059 100644
--- a/test/functional/page_objects/visualize_page.ts
+++ b/test/functional/page_objects/visualize_page.ts
@@ -47,6 +47,14 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide
await listingTable.clickNewButton('createVisualizationPromptButton');
}
+ public async clickAggBasedVisualizations() {
+ await testSubjects.click('visGroupAggBasedExploreLink');
+ }
+
+ public async goBackToGroups() {
+ await testSubjects.click('goBackLink');
+ }
+
public async createVisualizationPromptButton() {
await testSubjects.click('createVisualizationPromptButton');
}
@@ -59,6 +67,21 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide
.map((chart) => $(chart).findTestSubject('visTypeTitle').text().trim());
}
+ public async getPromotedVisTypes() {
+ const chartTypeField = await testSubjects.find('visNewDialogGroups');
+ const $ = await chartTypeField.parseDomContent();
+ const promotedVisTypes: string[] = [];
+ $('button')
+ .toArray()
+ .forEach((chart) => {
+ const title = $(chart).findTestSubject('visTypeTitle').text().trim();
+ if (title) {
+ promotedVisTypes.push(title);
+ }
+ });
+ return promotedVisTypes;
+ }
+
public async waitForVisualizationSelectPage() {
await retry.try(async () => {
const visualizeSelectTypePage = await testSubjects.find('visNewDialogTypes');
@@ -68,10 +91,27 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide
});
}
+ public async waitForGroupsSelectPage() {
+ await retry.try(async () => {
+ const visualizeSelectGroupStep = await testSubjects.find('visNewDialogGroups');
+ if (!(await visualizeSelectGroupStep.isDisplayed())) {
+ throw new Error('wait for vis groups select step');
+ }
+ });
+ }
+
public async navigateToNewVisualization() {
await common.navigateToApp('visualize');
await header.waitUntilLoadingHasFinished();
await this.clickNewVisualization();
+ await this.waitForGroupsSelectPage();
+ }
+
+ public async navigateToNewAggBasedVisualization() {
+ await common.navigateToApp('visualize');
+ await header.waitUntilLoadingHasFinished();
+ await this.clickNewVisualization();
+ await this.clickAggBasedVisualizations();
await this.waitForVisualizationSelectPage();
}
diff --git a/test/functional/services/dashboard/visualizations.ts b/test/functional/services/dashboard/visualizations.ts
index a5c16010d3eba..b35ef1e8f2f9a 100644
--- a/test/functional/services/dashboard/visualizations.ts
+++ b/test/functional/services/dashboard/visualizations.ts
@@ -147,6 +147,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }: F
await PageObjects.dashboard.switchToEditMode();
}
await this.ensureNewVisualizationDialogIsShowing();
+ await PageObjects.visualize.clickAggBasedVisualizations();
await PageObjects.visualize.clickMetric();
await find.clickByCssSelector('li.euiListGroupItem:nth-of-type(2)');
await testSubjects.exists('visualizeSaveButton');
diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts
index 1c0bf7ad46df1..e256cf14541a7 100644
--- a/test/functional/services/inspector.ts
+++ b/test/functional/services/inspector.ts
@@ -23,6 +23,7 @@ import { FtrProviderContext } from '../ftr_provider_context';
export function InspectorProvider({ getService }: FtrProviderContext) {
const log = getService('log');
const retry = getService('retry');
+ const browser = getService('browser');
const renderable = getService('renderable');
const flyout = getService('flyout');
const testSubjects = getService('testSubjects');
@@ -245,6 +246,18 @@ export function InspectorProvider({ getService }: FtrProviderContext) {
public getOpenRequestDetailResponseButton() {
return testSubjects.find('inspectorRequestDetailResponse');
}
+
+ public async getCodeEditorValue() {
+ let request: string = '';
+
+ await retry.try(async () => {
+ request = await browser.execute(
+ () => (window as any).monaco.editor.getModels()[0].getValue() as string
+ );
+ });
+
+ return request;
+ }
}
return new Inspector();
diff --git a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js
index 0846780f75ff6..1971754d5304e 100644
--- a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js
+++ b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js
@@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) {
describe('self changing vis', function describeIndexTests() {
before(async () => {
- await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickVisType('self_changing_vis');
});
diff --git a/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx b/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx
index 9c420f4425d04..a5d158fca836b 100644
--- a/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx
+++ b/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx
@@ -22,6 +22,7 @@ export function getAlertType(): AlertTypeModel {
name: 'Always Fires',
description: 'Alert when called',
iconClass: 'bolt',
+ documentationUrl: null,
alertParamsExpression: AlwaysFiringExpression,
validate: (alertParams: AlwaysFiringParamsProps['alertParams']) => {
const { instances } = alertParams;
diff --git a/x-pack/examples/alerting_example/public/alert_types/astros.tsx b/x-pack/examples/alerting_example/public/alert_types/astros.tsx
index 343f6b10ef85b..54f989b93e22f 100644
--- a/x-pack/examples/alerting_example/public/alert_types/astros.tsx
+++ b/x-pack/examples/alerting_example/public/alert_types/astros.tsx
@@ -47,6 +47,7 @@ export function getAlertType(): AlertTypeModel {
name: 'People Are In Space Right Now',
description: 'Alert when people are in space right now',
iconClass: 'globe',
+ documentationUrl: null,
alertParamsExpression: PeopleinSpaceExpression,
validate: (alertParams: PeopleinSpaceParamsProps['alertParams']) => {
const { outerSpaceCapacity, craft, op } = alertParams;
@@ -126,9 +127,9 @@ export const PeopleinSpaceExpression: React.FunctionComponent
- errs.map((e) => (
-
+ Object.entries(errors).map(([field, errs]: [string, string[]], fieldIndex) =>
+ errs.map((e, index) => (
+
{field}: `: ${errs}`
))
diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts
index bb1cb0d97689b..d02406a23045e 100644
--- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts
+++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts
@@ -5,25 +5,31 @@
*/
import uuid from 'uuid';
-import { range } from 'lodash';
+import { range, random } from 'lodash';
import { AlertType } from '../../../../plugins/alerts/server';
import { DEFAULT_INSTANCES_TO_GENERATE, ALERTING_EXAMPLE_APP_ID } from '../../common/constants';
+const ACTION_GROUPS = [
+ { id: 'small', name: 'small' },
+ { id: 'medium', name: 'medium' },
+ { id: 'large', name: 'large' },
+];
+
export const alertType: AlertType = {
id: 'example.always-firing',
name: 'Always firing',
- actionGroups: [{ id: 'default', name: 'default' }],
- defaultActionGroupId: 'default',
+ actionGroups: ACTION_GROUPS,
+ defaultActionGroupId: 'small',
async executor({ services, params: { instances = DEFAULT_INSTANCES_TO_GENERATE }, state }) {
const count = (state.count ?? 0) + 1;
range(instances)
- .map(() => ({ id: uuid.v4() }))
- .forEach((instance: { id: string }) => {
+ .map(() => ({ id: uuid.v4(), tshirtSize: ACTION_GROUPS[random(0, 2)].id! }))
+ .forEach((instance: { id: string; tshirtSize: string }) => {
services
.alertInstanceFactory(instance.id)
.replaceState({ triggerdOnCycle: count })
- .scheduleActions('default');
+ .scheduleActions(instance.tshirtSize);
});
return {
diff --git a/x-pack/plugins/alerts/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts
index 79e6bb8f2cbba..97a9a58400e38 100644
--- a/x-pack/plugins/alerts/common/alert.ts
+++ b/x-pack/plugins/alerts/common/alert.ts
@@ -20,13 +20,12 @@ export interface IntervalSchedule extends SavedObjectAttributes {
export const AlertExecutionStatusValues = ['ok', 'active', 'error', 'pending', 'unknown'] as const;
export type AlertExecutionStatuses = typeof AlertExecutionStatusValues[number];
-export const AlertExecutionStatusErrorReasonValues = [
- 'read',
- 'decrypt',
- 'execute',
- 'unknown',
-] as const;
-export type AlertExecutionStatusErrorReasons = typeof AlertExecutionStatusErrorReasonValues[number];
+export enum AlertExecutionStatusErrorReasons {
+ Read = 'read',
+ Decrypt = 'decrypt',
+ Execute = 'execute',
+ Unknown = 'unknown',
+}
export interface AlertExecutionStatus {
status: AlertExecutionStatuses;
@@ -74,3 +73,24 @@ export interface Alert {
}
export type SanitizedAlert = Omit;
+
+export enum HealthStatus {
+ OK = 'ok',
+ Warning = 'warn',
+ Error = 'error',
+}
+
+export interface AlertsHealth {
+ decryptionHealth: {
+ status: HealthStatus;
+ timestamp: string;
+ };
+ executionHealth: {
+ status: HealthStatus;
+ timestamp: string;
+ };
+ readHealth: {
+ status: HealthStatus;
+ timestamp: string;
+ };
+}
diff --git a/x-pack/plugins/alerts/common/alert_instance_summary.ts b/x-pack/plugins/alerts/common/alert_instance_summary.ts
index 08c3b2fc2c241..1aa183a141eab 100644
--- a/x-pack/plugins/alerts/common/alert_instance_summary.ts
+++ b/x-pack/plugins/alerts/common/alert_instance_summary.ts
@@ -27,5 +27,6 @@ export interface AlertInstanceSummary {
export interface AlertInstanceStatus {
status: AlertInstanceStatusValues;
muted: boolean;
+ actionGroupId?: string;
activeStartDate?: string;
}
diff --git a/x-pack/plugins/alerts/common/index.ts b/x-pack/plugins/alerts/common/index.ts
index ab71f77a049f6..65aeec840da7e 100644
--- a/x-pack/plugins/alerts/common/index.ts
+++ b/x-pack/plugins/alerts/common/index.ts
@@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { AlertsHealth } from './alert';
+
export * from './alert';
export * from './alert_type';
export * from './alert_instance';
@@ -19,6 +21,7 @@ export interface ActionGroup {
export interface AlertingFrameworkHealth {
isSufficientlySecure: boolean;
hasPermanentEncryptionKey: boolean;
+ alertingFrameworkHeath: AlertsHealth;
}
export const BASE_ALERT_API_PATH = '/api/alerts';
diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts
index a53e49337f385..9cb2a33222d23 100644
--- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts
+++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts
@@ -118,12 +118,12 @@ describe('getAlertInstanceSummary()', () => {
.addExecute()
.addNewInstance('instance-currently-active')
.addNewInstance('instance-previously-active')
- .addActiveInstance('instance-currently-active')
- .addActiveInstance('instance-previously-active')
+ .addActiveInstance('instance-currently-active', 'action group A')
+ .addActiveInstance('instance-previously-active', 'action group B')
.advanceTime(10000)
.addExecute()
.addResolvedInstance('instance-previously-active')
- .addActiveInstance('instance-currently-active')
+ .addActiveInstance('instance-currently-active', 'action group A')
.getEvents();
const eventsResult = {
...AlertInstanceSummaryFindEventsResult,
@@ -144,16 +144,19 @@ describe('getAlertInstanceSummary()', () => {
"id": "1",
"instances": Object {
"instance-currently-active": Object {
+ "actionGroupId": "action group A",
"activeStartDate": "2019-02-12T21:01:22.479Z",
"muted": false,
"status": "Active",
},
"instance-muted-no-activity": Object {
+ "actionGroupId": undefined,
"activeStartDate": undefined,
"muted": true,
"status": "OK",
},
"instance-previously-active": Object {
+ "actionGroupId": undefined,
"activeStartDate": undefined,
"muted": false,
"status": "OK",
diff --git a/x-pack/plugins/alerts/server/config.test.ts b/x-pack/plugins/alerts/server/config.test.ts
new file mode 100644
index 0000000000000..93aa3c38a0460
--- /dev/null
+++ b/x-pack/plugins/alerts/server/config.test.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { configSchema } from './config';
+
+describe('config validation', () => {
+ test('alerts defaults', () => {
+ const config: Record = {};
+ expect(configSchema.validate(config)).toMatchInlineSnapshot(`
+ Object {
+ "healthCheck": Object {
+ "interval": "60m",
+ },
+ }
+ `);
+ });
+});
diff --git a/x-pack/plugins/alerts/server/config.ts b/x-pack/plugins/alerts/server/config.ts
new file mode 100644
index 0000000000000..a6d2196a407b5
--- /dev/null
+++ b/x-pack/plugins/alerts/server/config.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { schema, TypeOf } from '@kbn/config-schema';
+import { validateDurationSchema } from './lib';
+
+export const configSchema = schema.object({
+ healthCheck: schema.object({
+ interval: schema.string({ validate: validateDurationSchema, defaultValue: '60m' }),
+ }),
+});
+
+export type AlertsConfig = TypeOf;
diff --git a/x-pack/plugins/alerts/server/health/get_health.test.ts b/x-pack/plugins/alerts/server/health/get_health.test.ts
new file mode 100644
index 0000000000000..34517a89f04d9
--- /dev/null
+++ b/x-pack/plugins/alerts/server/health/get_health.test.ts
@@ -0,0 +1,221 @@
+/*
+ * 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 { savedObjectsRepositoryMock } from '../../../../../src/core/server/mocks';
+import { AlertExecutionStatusErrorReasons, HealthStatus } from '../types';
+import { getHealth } from './get_health';
+
+const savedObjectsRepository = savedObjectsRepositoryMock.create();
+
+describe('getHealth()', () => {
+ test('return true if some of alerts has a decryption error', async () => {
+ const lastExecutionDateError = new Date().toISOString();
+ const lastExecutionDate = new Date().toISOString();
+ savedObjectsRepository.find.mockResolvedValueOnce({
+ total: 1,
+ per_page: 1,
+ page: 1,
+ saved_objects: [
+ {
+ id: '1',
+ type: 'alert',
+ attributes: {
+ alertTypeId: 'myType',
+ schedule: { interval: '10s' },
+ params: {
+ bar: true,
+ },
+ createdAt: new Date().toISOString(),
+ actions: [
+ {
+ group: 'default',
+ actionRef: 'action_0',
+ params: {
+ foo: true,
+ },
+ },
+ ],
+ executionStatus: {
+ status: 'error',
+ lastExecutionDate: lastExecutionDateError,
+ error: {
+ reason: AlertExecutionStatusErrorReasons.Decrypt,
+ message: 'Failed decrypt',
+ },
+ },
+ },
+ score: 1,
+ references: [
+ {
+ name: 'action_0',
+ type: 'action',
+ id: '1',
+ },
+ ],
+ },
+ ],
+ });
+ savedObjectsRepository.find.mockResolvedValueOnce({
+ total: 0,
+ per_page: 10,
+ page: 1,
+ saved_objects: [],
+ });
+
+ savedObjectsRepository.find.mockResolvedValueOnce({
+ total: 0,
+ per_page: 10,
+ page: 1,
+ saved_objects: [],
+ });
+
+ savedObjectsRepository.find.mockResolvedValueOnce({
+ total: 1,
+ per_page: 1,
+ page: 1,
+ saved_objects: [
+ {
+ id: '2',
+ type: 'alert',
+ attributes: {
+ alertTypeId: 'myType',
+ schedule: { interval: '1s' },
+ params: {
+ bar: true,
+ },
+ createdAt: new Date().toISOString(),
+ actions: [],
+ executionStatus: {
+ status: 'ok',
+ lastExecutionDate,
+ },
+ },
+ score: 1,
+ references: [],
+ },
+ ],
+ });
+ const result = await getHealth(savedObjectsRepository);
+ expect(result).toStrictEqual({
+ executionHealth: {
+ status: HealthStatus.OK,
+ timestamp: lastExecutionDate,
+ },
+ readHealth: {
+ status: HealthStatus.OK,
+ timestamp: lastExecutionDate,
+ },
+ decryptionHealth: {
+ status: HealthStatus.Warning,
+ timestamp: lastExecutionDateError,
+ },
+ });
+ expect(savedObjectsRepository.find).toHaveBeenCalledTimes(4);
+ });
+
+ test('return false if no alerts with a decryption error', async () => {
+ const lastExecutionDateError = new Date().toISOString();
+ const lastExecutionDate = new Date().toISOString();
+ savedObjectsRepository.find.mockResolvedValueOnce({
+ total: 0,
+ per_page: 10,
+ page: 1,
+ saved_objects: [],
+ });
+
+ savedObjectsRepository.find.mockResolvedValueOnce({
+ total: 1,
+ per_page: 1,
+ page: 1,
+ saved_objects: [
+ {
+ id: '1',
+ type: 'alert',
+ attributes: {
+ alertTypeId: 'myType',
+ schedule: { interval: '10s' },
+ params: {
+ bar: true,
+ },
+ createdAt: new Date().toISOString(),
+ actions: [
+ {
+ group: 'default',
+ actionRef: 'action_0',
+ params: {
+ foo: true,
+ },
+ },
+ ],
+ executionStatus: {
+ status: 'error',
+ lastExecutionDate: lastExecutionDateError,
+ error: {
+ reason: AlertExecutionStatusErrorReasons.Execute,
+ message: 'Failed',
+ },
+ },
+ },
+ score: 1,
+ references: [
+ {
+ name: 'action_0',
+ type: 'action',
+ id: '1',
+ },
+ ],
+ },
+ ],
+ });
+ savedObjectsRepository.find.mockResolvedValueOnce({
+ total: 0,
+ per_page: 10,
+ page: 1,
+ saved_objects: [],
+ });
+
+ savedObjectsRepository.find.mockResolvedValueOnce({
+ total: 1,
+ per_page: 1,
+ page: 1,
+ saved_objects: [
+ {
+ id: '2',
+ type: 'alert',
+ attributes: {
+ alertTypeId: 'myType',
+ schedule: { interval: '1s' },
+ params: {
+ bar: true,
+ },
+ createdAt: new Date().toISOString(),
+ actions: [],
+ executionStatus: {
+ status: 'ok',
+ lastExecutionDate,
+ },
+ },
+ score: 1,
+ references: [],
+ },
+ ],
+ });
+ const result = await getHealth(savedObjectsRepository);
+ expect(result).toStrictEqual({
+ executionHealth: {
+ status: HealthStatus.Warning,
+ timestamp: lastExecutionDateError,
+ },
+ readHealth: {
+ status: HealthStatus.OK,
+ timestamp: lastExecutionDate,
+ },
+ decryptionHealth: {
+ status: HealthStatus.OK,
+ timestamp: lastExecutionDate,
+ },
+ });
+ });
+});
diff --git a/x-pack/plugins/alerts/server/health/get_health.ts b/x-pack/plugins/alerts/server/health/get_health.ts
new file mode 100644
index 0000000000000..b7b4582aa8d10
--- /dev/null
+++ b/x-pack/plugins/alerts/server/health/get_health.ts
@@ -0,0 +1,97 @@
+/*
+ * 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 { ISavedObjectsRepository } from 'src/core/server';
+import { AlertsHealth, HealthStatus, RawAlert, AlertExecutionStatusErrorReasons } from '../types';
+
+export const getHealth = async (
+ internalSavedObjectsRepository: ISavedObjectsRepository
+): Promise => {
+ const healthStatuses = {
+ decryptionHealth: {
+ status: HealthStatus.OK,
+ timestamp: '',
+ },
+ executionHealth: {
+ status: HealthStatus.OK,
+ timestamp: '',
+ },
+ readHealth: {
+ status: HealthStatus.OK,
+ timestamp: '',
+ },
+ };
+
+ const { saved_objects: decryptErrorData } = await internalSavedObjectsRepository.find({
+ filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Decrypt}`,
+ fields: ['executionStatus'],
+ type: 'alert',
+ sortField: 'executionStatus.lastExecutionDate',
+ sortOrder: 'desc',
+ page: 1,
+ perPage: 1,
+ });
+
+ if (decryptErrorData.length > 0) {
+ healthStatuses.decryptionHealth = {
+ status: HealthStatus.Warning,
+ timestamp: decryptErrorData[0].attributes.executionStatus.lastExecutionDate,
+ };
+ }
+
+ const { saved_objects: executeErrorData } = await internalSavedObjectsRepository.find({
+ filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Execute}`,
+ fields: ['executionStatus'],
+ type: 'alert',
+ sortField: 'executionStatus.lastExecutionDate',
+ sortOrder: 'desc',
+ page: 1,
+ perPage: 1,
+ });
+
+ if (executeErrorData.length > 0) {
+ healthStatuses.executionHealth = {
+ status: HealthStatus.Warning,
+ timestamp: executeErrorData[0].attributes.executionStatus.lastExecutionDate,
+ };
+ }
+
+ const { saved_objects: readErrorData } = await internalSavedObjectsRepository.find({
+ filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Read}`,
+ fields: ['executionStatus'],
+ type: 'alert',
+ sortField: 'executionStatus.lastExecutionDate',
+ sortOrder: 'desc',
+ page: 1,
+ perPage: 1,
+ });
+
+ if (readErrorData.length > 0) {
+ healthStatuses.readHealth = {
+ status: HealthStatus.Warning,
+ timestamp: readErrorData[0].attributes.executionStatus.lastExecutionDate,
+ };
+ }
+
+ const { saved_objects: noErrorData } = await internalSavedObjectsRepository.find({
+ filter: 'not alert.attributes.executionStatus.status:error',
+ fields: ['executionStatus'],
+ type: 'alert',
+ sortField: 'executionStatus.lastExecutionDate',
+ sortOrder: 'desc',
+ });
+ const lastExecutionDate =
+ noErrorData.length > 0
+ ? noErrorData[0].attributes.executionStatus.lastExecutionDate
+ : new Date().toISOString();
+
+ for (const [, statusItem] of Object.entries(healthStatuses)) {
+ if (statusItem.status === HealthStatus.OK) {
+ statusItem.timestamp = lastExecutionDate;
+ }
+ }
+
+ return healthStatuses;
+};
diff --git a/x-pack/plugins/alerts/server/health/get_state.test.ts b/x-pack/plugins/alerts/server/health/get_state.test.ts
new file mode 100644
index 0000000000000..86981c486da0f
--- /dev/null
+++ b/x-pack/plugins/alerts/server/health/get_state.test.ts
@@ -0,0 +1,75 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { taskManagerMock } from '../../../task_manager/server/mocks';
+import { getHealthStatusStream } from '.';
+import { TaskStatus } from '../../../task_manager/server';
+import { HealthStatus } from '../types';
+
+describe('getHealthStatusStream()', () => {
+ const mockTaskManager = taskManagerMock.createStart();
+
+ it('should return an object with the "unavailable" level and proper summary of "Alerting framework is unhealthy"', async () => {
+ mockTaskManager.get.mockReturnValue(
+ new Promise((_resolve, _reject) => {
+ return {
+ id: 'test',
+ attempts: 0,
+ status: TaskStatus.Running,
+ version: '123',
+ runAt: new Date(),
+ scheduledAt: new Date(),
+ startedAt: new Date(),
+ retryAt: new Date(Date.now() + 5 * 60 * 1000),
+ state: {
+ runs: 1,
+ health_status: HealthStatus.Warning,
+ },
+ taskType: 'alerting:alerting_health_check',
+ params: {
+ alertId: '1',
+ },
+ ownerId: null,
+ };
+ })
+ );
+ getHealthStatusStream(mockTaskManager).subscribe(
+ (val: { level: Readonly; summary: string }) => {
+ expect(val.level).toBe(false);
+ }
+ );
+ });
+
+ it('should return an object with the "available" level and proper summary of "Alerting framework is healthy"', async () => {
+ mockTaskManager.get.mockReturnValue(
+ new Promise((_resolve, _reject) => {
+ return {
+ id: 'test',
+ attempts: 0,
+ status: TaskStatus.Running,
+ version: '123',
+ runAt: new Date(),
+ scheduledAt: new Date(),
+ startedAt: new Date(),
+ retryAt: new Date(Date.now() + 5 * 60 * 1000),
+ state: {
+ runs: 1,
+ health_status: HealthStatus.OK,
+ },
+ taskType: 'alerting:alerting_health_check',
+ params: {
+ alertId: '1',
+ },
+ ownerId: null,
+ };
+ })
+ );
+ getHealthStatusStream(mockTaskManager).subscribe(
+ (val: { level: Readonly; summary: string }) => {
+ expect(val.level).toBe(true);
+ }
+ );
+ });
+});
diff --git a/x-pack/plugins/alerts/server/health/get_state.ts b/x-pack/plugins/alerts/server/health/get_state.ts
new file mode 100644
index 0000000000000..476456ecad88a
--- /dev/null
+++ b/x-pack/plugins/alerts/server/health/get_state.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { interval, Observable } from 'rxjs';
+import { catchError, switchMap } from 'rxjs/operators';
+import { ServiceStatus, ServiceStatusLevels } from '../../../../../src/core/server';
+import { TaskManagerStartContract } from '../../../task_manager/server';
+import { HEALTH_TASK_ID } from './task';
+import { HealthStatus } from '../types';
+
+async function getLatestTaskState(taskManager: TaskManagerStartContract) {
+ try {
+ const result = await taskManager.get(HEALTH_TASK_ID);
+ return result;
+ } catch (err) {
+ const errMessage = err && err.message ? err.message : err.toString();
+ if (!errMessage.includes('NotInitialized')) {
+ throw err;
+ }
+ }
+
+ return null;
+}
+
+const LEVEL_SUMMARY = {
+ [ServiceStatusLevels.available.toString()]: i18n.translate(
+ 'xpack.alerts.server.healthStatus.available',
+ {
+ defaultMessage: 'Alerting framework is available',
+ }
+ ),
+ [ServiceStatusLevels.degraded.toString()]: i18n.translate(
+ 'xpack.alerts.server.healthStatus.degraded',
+ {
+ defaultMessage: 'Alerting framework is degraded',
+ }
+ ),
+ [ServiceStatusLevels.unavailable.toString()]: i18n.translate(
+ 'xpack.alerts.server.healthStatus.unavailable',
+ {
+ defaultMessage: 'Alerting framework is unavailable',
+ }
+ ),
+};
+
+export const getHealthStatusStream = (
+ taskManager: TaskManagerStartContract
+): Observable> => {
+ return interval(60000 * 5).pipe(
+ switchMap(async () => {
+ const doc = await getLatestTaskState(taskManager);
+ const level =
+ doc?.state?.health_status === HealthStatus.OK
+ ? ServiceStatusLevels.available
+ : doc?.state?.health_status === HealthStatus.Warning
+ ? ServiceStatusLevels.degraded
+ : ServiceStatusLevels.unavailable;
+ return {
+ level,
+ summary: LEVEL_SUMMARY[level.toString()],
+ };
+ }),
+ catchError(async (error) => ({
+ level: ServiceStatusLevels.unavailable,
+ summary: LEVEL_SUMMARY[ServiceStatusLevels.unavailable.toString()],
+ meta: { error },
+ }))
+ );
+};
diff --git a/x-pack/plugins/alerts/server/health/index.ts b/x-pack/plugins/alerts/server/health/index.ts
new file mode 100644
index 0000000000000..730c4596aa550
--- /dev/null
+++ b/x-pack/plugins/alerts/server/health/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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.
+ */
+
+export { getHealthStatusStream } from './get_state';
+export { scheduleAlertingHealthCheck, initializeAlertingHealth } from './task';
diff --git a/x-pack/plugins/alerts/server/health/task.ts b/x-pack/plugins/alerts/server/health/task.ts
new file mode 100644
index 0000000000000..6ea01a1083c13
--- /dev/null
+++ b/x-pack/plugins/alerts/server/health/task.ts
@@ -0,0 +1,94 @@
+/*
+ * 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 { CoreStart, Logger } from 'kibana/server';
+import {
+ RunContext,
+ TaskManagerSetupContract,
+ TaskManagerStartContract,
+} from '../../../task_manager/server';
+import { AlertsConfig } from '../config';
+import { AlertingPluginsStart } from '../plugin';
+import { HealthStatus } from '../types';
+import { getHealth } from './get_health';
+
+export const HEALTH_TASK_TYPE = 'alerting_health_check';
+
+export const HEALTH_TASK_ID = `Alerting-${HEALTH_TASK_TYPE}`;
+
+export function initializeAlertingHealth(
+ logger: Logger,
+ taskManager: TaskManagerSetupContract,
+ coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]>
+) {
+ registerAlertingHealthCheckTask(logger, taskManager, coreStartServices);
+}
+
+export async function scheduleAlertingHealthCheck(
+ logger: Logger,
+ config: Promise,
+ taskManager: TaskManagerStartContract
+) {
+ try {
+ const interval = (await config).healthCheck.interval;
+ await taskManager.ensureScheduled({
+ id: HEALTH_TASK_ID,
+ taskType: HEALTH_TASK_TYPE,
+ schedule: {
+ interval,
+ },
+ state: {},
+ params: {},
+ });
+ } catch (e) {
+ logger.debug(`Error scheduling task, received ${e.message}`);
+ }
+}
+
+function registerAlertingHealthCheckTask(
+ logger: Logger,
+ taskManager: TaskManagerSetupContract,
+ coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]>
+) {
+ taskManager.registerTaskDefinitions({
+ [HEALTH_TASK_TYPE]: {
+ title: 'Alerting framework health check task',
+ createTaskRunner: healthCheckTaskRunner(logger, coreStartServices),
+ },
+ });
+}
+
+export function healthCheckTaskRunner(
+ logger: Logger,
+ coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]>
+) {
+ return ({ taskInstance }: RunContext) => {
+ const { state } = taskInstance;
+ return {
+ async run() {
+ try {
+ const alertingHealthStatus = await getHealth(
+ (await coreStartServices)[0].savedObjects.createInternalRepository(['alert'])
+ );
+ return {
+ state: {
+ runs: (state.runs || 0) + 1,
+ health_status: alertingHealthStatus.decryptionHealth.status,
+ },
+ };
+ } catch (errMsg) {
+ logger.warn(`Error executing alerting health check task: ${errMsg}`);
+ return {
+ state: {
+ runs: (state.runs || 0) + 1,
+ health_status: HealthStatus.Error,
+ },
+ };
+ }
+ },
+ };
+ };
+}
diff --git a/x-pack/plugins/alerts/server/index.ts b/x-pack/plugins/alerts/server/index.ts
index 1e442c5196cf2..64e585da5c654 100644
--- a/x-pack/plugins/alerts/server/index.ts
+++ b/x-pack/plugins/alerts/server/index.ts
@@ -5,8 +5,10 @@
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
import { AlertsClient as AlertsClientClass } from './alerts_client';
-import { PluginInitializerContext } from '../../../../src/core/server';
+import { PluginConfigDescriptor, PluginInitializerContext } from '../../../../src/core/server';
import { AlertingPlugin } from './plugin';
+import { configSchema } from './config';
+import { AlertsConfigType } from './types';
export type AlertsClient = PublicMethodsOf;
@@ -30,3 +32,7 @@ export { AlertInstance } from './alert_instance';
export { parseDuration } from './lib';
export const plugin = (initContext: PluginInitializerContext) => new AlertingPlugin(initContext);
+
+export const config: PluginConfigDescriptor = {
+ schema: configSchema,
+};
diff --git a/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts b/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts
index 3372d19cd4090..bb24ab034d0dd 100644
--- a/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts
+++ b/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts
@@ -57,7 +57,9 @@ describe('AlertExecutionStatus', () => {
});
test('error with a reason', () => {
- const status = executionStatusFromError(new ErrorWithReason('execute', new Error('hoo!')));
+ const status = executionStatusFromError(
+ new ErrorWithReason(AlertExecutionStatusErrorReasons.Execute, new Error('hoo!'))
+ );
expect(status.status).toBe('error');
expect(status.error).toMatchInlineSnapshot(`
Object {
@@ -71,7 +73,7 @@ describe('AlertExecutionStatus', () => {
describe('alertExecutionStatusToRaw()', () => {
const date = new Date('2020-09-03T16:26:58Z');
const status = 'ok';
- const reason: AlertExecutionStatusErrorReasons = 'decrypt';
+ const reason = AlertExecutionStatusErrorReasons.Decrypt;
const error = { reason, message: 'wops' };
test('status without an error', () => {
@@ -102,7 +104,7 @@ describe('AlertExecutionStatus', () => {
describe('alertExecutionStatusFromRaw()', () => {
const date = new Date('2020-09-03T16:26:58Z').toISOString();
const status = 'active';
- const reason: AlertExecutionStatusErrorReasons = 'execute';
+ const reason = AlertExecutionStatusErrorReasons.Execute;
const error = { reason, message: 'wops' };
test('no input', () => {
diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts
index 566a1770c0658..f9e4a2908d6ce 100644
--- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts
+++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts
@@ -104,11 +104,13 @@ describe('alertInstanceSummaryFromEventLog', () => {
Object {
"instances": Object {
"instance-1": Object {
+ "actionGroupId": undefined,
"activeStartDate": undefined,
"muted": true,
"status": "OK",
},
"instance-2": Object {
+ "actionGroupId": undefined,
"activeStartDate": undefined,
"muted": true,
"status": "OK",
@@ -184,7 +186,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
.advanceTime(10000)
.addExecute()
.addResolvedInstance('instance-1')
@@ -202,6 +204,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
Object {
"instances": Object {
"instance-1": Object {
+ "actionGroupId": undefined,
"activeStartDate": undefined,
"muted": false,
"status": "OK",
@@ -218,7 +221,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
.advanceTime(10000)
.addExecute()
.addResolvedInstance('instance-1')
@@ -236,6 +239,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
Object {
"instances": Object {
"instance-1": Object {
+ "actionGroupId": undefined,
"activeStartDate": undefined,
"muted": false,
"status": "OK",
@@ -253,10 +257,10 @@ describe('alertInstanceSummaryFromEventLog', () => {
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
.advanceTime(10000)
.addExecute()
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
@@ -271,6 +275,79 @@ describe('alertInstanceSummaryFromEventLog', () => {
Object {
"instances": Object {
"instance-1": Object {
+ "actionGroupId": "action group A",
+ "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 with no action group in event log', async () => {
+ const alert = createAlert({});
+ const eventsFactory = new EventsFactory();
+ const events = eventsFactory
+ .addExecute()
+ .addNewInstance('instance-1')
+ .addActiveInstance('instance-1', undefined)
+ .advanceTime(10000)
+ .addExecute()
+ .addActiveInstance('instance-1', undefined)
+ .getEvents();
+
+ const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
+ alert,
+ events,
+ dateStart,
+ dateEnd,
+ });
+
+ const { lastRun, status, instances } = summary;
+ expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
+ Object {
+ "instances": Object {
+ "instance-1": Object {
+ "actionGroupId": undefined,
+ "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 that switched action groups', async () => {
+ const alert = createAlert({});
+ const eventsFactory = new EventsFactory();
+ const events = eventsFactory
+ .addExecute()
+ .addNewInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
+ .advanceTime(10000)
+ .addExecute()
+ .addActiveInstance('instance-1', 'action group B')
+ .getEvents();
+
+ const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
+ alert,
+ events,
+ dateStart,
+ dateEnd,
+ });
+
+ const { lastRun, status, instances } = summary;
+ expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
+ Object {
+ "instances": Object {
+ "instance-1": Object {
+ "actionGroupId": "action group B",
"activeStartDate": "2020-06-18T00:00:00.000Z",
"muted": false,
"status": "Active",
@@ -287,10 +364,10 @@ describe('alertInstanceSummaryFromEventLog', () => {
const eventsFactory = new EventsFactory();
const events = eventsFactory
.addExecute()
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
.advanceTime(10000)
.addExecute()
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
@@ -305,6 +382,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
Object {
"instances": Object {
"instance-1": Object {
+ "actionGroupId": "action group A",
"activeStartDate": undefined,
"muted": false,
"status": "Active",
@@ -322,12 +400,12 @@ describe('alertInstanceSummaryFromEventLog', () => {
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
.addNewInstance('instance-2')
- .addActiveInstance('instance-2')
+ .addActiveInstance('instance-2', 'action group B')
.advanceTime(10000)
.addExecute()
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
.addResolvedInstance('instance-2')
.getEvents();
@@ -343,11 +421,13 @@ describe('alertInstanceSummaryFromEventLog', () => {
Object {
"instances": Object {
"instance-1": Object {
+ "actionGroupId": "action group A",
"activeStartDate": "2020-06-18T00:00:00.000Z",
"muted": true,
"status": "Active",
},
"instance-2": Object {
+ "actionGroupId": undefined,
"activeStartDate": undefined,
"muted": true,
"status": "OK",
@@ -365,19 +445,19 @@ describe('alertInstanceSummaryFromEventLog', () => {
const events = eventsFactory
.addExecute()
.addNewInstance('instance-1')
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
.addNewInstance('instance-2')
- .addActiveInstance('instance-2')
+ .addActiveInstance('instance-2', 'action group B')
.advanceTime(10000)
.addExecute()
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group A')
.addResolvedInstance('instance-2')
.advanceTime(10000)
.addExecute()
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group B')
.advanceTime(10000)
.addExecute()
- .addActiveInstance('instance-1')
+ .addActiveInstance('instance-1', 'action group B')
.getEvents();
const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({
@@ -392,11 +472,13 @@ describe('alertInstanceSummaryFromEventLog', () => {
Object {
"instances": Object {
"instance-1": Object {
+ "actionGroupId": "action group B",
"activeStartDate": "2020-06-18T00:00:00.000Z",
"muted": false,
"status": "Active",
},
"instance-2": Object {
+ "actionGroupId": undefined,
"activeStartDate": undefined,
"muted": false,
"status": "OK",
@@ -452,14 +534,17 @@ export class EventsFactory {
return this;
}
- addActiveInstance(instanceId: string): EventsFactory {
+ addActiveInstance(instanceId: string, actionGroupId: string | undefined): EventsFactory {
+ const kibanaAlerting = actionGroupId
+ ? { instance_id: instanceId, action_group_id: actionGroupId }
+ : { instance_id: instanceId };
this.events.push({
'@timestamp': this.date,
event: {
provider: EVENT_LOG_PROVIDER,
action: EVENT_LOG_ACTIONS.activeInstance,
},
- kibana: { alerting: { instance_id: instanceId } },
+ kibana: { alerting: kibanaAlerting },
});
return this;
}
diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts
index 9a5e870c8199a..8fed97a74435d 100644
--- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts
+++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts
@@ -78,10 +78,12 @@ export function alertInstanceSummaryFromEventLog(
// intentionally no break here
case EVENT_LOG_ACTIONS.activeInstance:
status.status = 'Active';
+ status.actionGroupId = event?.kibana?.alerting?.action_group_id;
break;
case EVENT_LOG_ACTIONS.resolvedInstance:
status.status = 'OK';
status.activeStartDate = undefined;
+ status.actionGroupId = undefined;
}
}
@@ -118,6 +120,7 @@ function getAlertInstanceStatus(
const status: AlertInstanceStatus = {
status: 'OK',
muted: false,
+ actionGroupId: undefined,
activeStartDate: undefined,
};
instances.set(instanceId, status);
diff --git a/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts b/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts
index f31f584400308..eff935966345f 100644
--- a/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts
+++ b/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts
@@ -5,20 +5,21 @@
*/
import { ErrorWithReason, getReasonFromError, isErrorWithReason } from './error_with_reason';
+import { AlertExecutionStatusErrorReasons } from '../types';
describe('ErrorWithReason', () => {
const plainError = new Error('well, actually');
- const errorWithReason = new ErrorWithReason('decrypt', plainError);
+ const errorWithReason = new ErrorWithReason(AlertExecutionStatusErrorReasons.Decrypt, plainError);
test('ErrorWithReason class', () => {
expect(errorWithReason.message).toBe(plainError.message);
expect(errorWithReason.error).toBe(plainError);
- expect(errorWithReason.reason).toBe('decrypt');
+ expect(errorWithReason.reason).toBe(AlertExecutionStatusErrorReasons.Decrypt);
});
test('getReasonFromError()', () => {
expect(getReasonFromError(plainError)).toBe('unknown');
- expect(getReasonFromError(errorWithReason)).toBe('decrypt');
+ expect(getReasonFromError(errorWithReason)).toBe(AlertExecutionStatusErrorReasons.Decrypt);
});
test('isErrorWithReason()', () => {
diff --git a/x-pack/plugins/alerts/server/lib/error_with_reason.ts b/x-pack/plugins/alerts/server/lib/error_with_reason.ts
index 29eb666e64427..a732b44ef2238 100644
--- a/x-pack/plugins/alerts/server/lib/error_with_reason.ts
+++ b/x-pack/plugins/alerts/server/lib/error_with_reason.ts
@@ -21,7 +21,7 @@ export function getReasonFromError(error: Error): AlertExecutionStatusErrorReaso
if (isErrorWithReason(error)) {
return error.reason;
}
- return 'unknown';
+ return AlertExecutionStatusErrorReasons.Unknown;
}
export function isErrorWithReason(error: Error | ErrorWithReason): error is ErrorWithReason {
diff --git a/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts b/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts
index b570957d82de4..ab21dc77fa251 100644
--- a/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts
+++ b/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts
@@ -8,6 +8,7 @@ import { isAlertSavedObjectNotFoundError } from './is_alert_not_found_error';
import { ErrorWithReason } from './error_with_reason';
import { SavedObjectsErrorHelpers } from '../../../../../src/core/server';
import uuid from 'uuid';
+import { AlertExecutionStatusErrorReasons } from '../types';
describe('isAlertSavedObjectNotFoundError', () => {
const id = uuid.v4();
@@ -25,7 +26,7 @@ describe('isAlertSavedObjectNotFoundError', () => {
});
test('identifies SavedObjects Not Found errors wrapped in an ErrorWithReason', () => {
- const error = new ErrorWithReason('read', errorSONF);
+ const error = new ErrorWithReason(AlertExecutionStatusErrorReasons.Read, errorSONF);
expect(isAlertSavedObjectNotFoundError(error, id)).toBe(true);
});
});
diff --git a/x-pack/plugins/alerts/server/mocks.ts b/x-pack/plugins/alerts/server/mocks.ts
index 05d64bdbb77f4..cfae4c650bd42 100644
--- a/x-pack/plugins/alerts/server/mocks.ts
+++ b/x-pack/plugins/alerts/server/mocks.ts
@@ -25,6 +25,7 @@ const createStartMock = () => {
const mock: jest.Mocked = {
listTypes: jest.fn(),
getAlertsClientWithRequest: jest.fn().mockResolvedValue(alertsClientMock.create()),
+ getFrameworkHealth: jest.fn(),
};
return mock;
};
diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts
index b13a1c62f6602..715fbc6aeed45 100644
--- a/x-pack/plugins/alerts/server/plugin.test.ts
+++ b/x-pack/plugins/alerts/server/plugin.test.ts
@@ -5,7 +5,7 @@
*/
import { AlertingPlugin, AlertingPluginsSetup, AlertingPluginsStart } from './plugin';
-import { coreMock } from '../../../../src/core/server/mocks';
+import { coreMock, statusServiceMock } from '../../../../src/core/server/mocks';
import { licensingMock } from '../../licensing/server/mocks';
import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks';
import { taskManagerMock } from '../../task_manager/server/mocks';
@@ -13,15 +13,21 @@ import { eventLogServiceMock } from '../../event_log/server/event_log_service.mo
import { KibanaRequest, CoreSetup } from 'kibana/server';
import { featuresPluginMock } from '../../features/server/mocks';
import { KibanaFeature } from '../../features/server';
+import { AlertsConfig } from './config';
describe('Alerting Plugin', () => {
describe('setup()', () => {
it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => {
- const context = coreMock.createPluginInitializerContext();
+ const context = coreMock.createPluginInitializerContext({
+ healthCheck: {
+ interval: '5m',
+ },
+ });
const plugin = new AlertingPlugin(context);
const coreSetup = coreMock.createSetup();
const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup();
+ const statusMock = statusServiceMock.createSetupContract();
await plugin.setup(
({
...coreSetup,
@@ -29,6 +35,7 @@ describe('Alerting Plugin', () => {
...coreSetup.http,
route: jest.fn(),
},
+ status: statusMock,
} as unknown) as CoreSetup,
({
licensing: licensingMock.createSetup(),
@@ -38,6 +45,7 @@ describe('Alerting Plugin', () => {
} as unknown) as AlertingPluginsSetup
);
+ expect(statusMock.set).toHaveBeenCalledTimes(1);
expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true);
expect(context.logger.get().warn).toHaveBeenCalledWith(
'APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.'
@@ -55,7 +63,11 @@ describe('Alerting Plugin', () => {
*/
describe('getAlertsClientWithRequest()', () => {
it('throws error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to true', async () => {
- const context = coreMock.createPluginInitializerContext();
+ const context = coreMock.createPluginInitializerContext({
+ healthCheck: {
+ interval: '5m',
+ },
+ });
const plugin = new AlertingPlugin(context);
const coreSetup = coreMock.createSetup();
@@ -98,7 +110,11 @@ describe('Alerting Plugin', () => {
});
it(`doesn't throw error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to false`, async () => {
- const context = coreMock.createPluginInitializerContext();
+ const context = coreMock.createPluginInitializerContext({
+ healthCheck: {
+ interval: '5m',
+ },
+ });
const plugin = new AlertingPlugin(context);
const coreSetup = coreMock.createSetup();
diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts
index 75873a2845c15..1fa89606a76fc 100644
--- a/x-pack/plugins/alerts/server/plugin.ts
+++ b/x-pack/plugins/alerts/server/plugin.ts
@@ -6,6 +6,7 @@
import type { PublicMethodsOf } from '@kbn/utility-types';
import { first, map } from 'rxjs/operators';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import { combineLatest } from 'rxjs';
import { SecurityPluginSetup } from '../../security/server';
import {
EncryptedSavedObjectsPluginSetup,
@@ -30,6 +31,8 @@ import {
SharedGlobalConfig,
ElasticsearchServiceStart,
ILegacyClusterClient,
+ StatusServiceSetup,
+ ServiceStatus,
} from '../../../../src/core/server';
import {
@@ -56,12 +59,19 @@ import {
PluginSetupContract as ActionsPluginSetupContract,
PluginStartContract as ActionsPluginStartContract,
} from '../../actions/server';
-import { Services } from './types';
+import { AlertsHealth, Services } from './types';
import { registerAlertsUsageCollector } from './usage';
import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task';
import { IEventLogger, IEventLogService, IEventLogClientService } from '../../event_log/server';
import { PluginStartContract as FeaturesPluginStart } from '../../features/server';
import { setupSavedObjects } from './saved_objects';
+import {
+ getHealthStatusStream,
+ scheduleAlertingHealthCheck,
+ initializeAlertingHealth,
+} from './health';
+import { AlertsConfig } from './config';
+import { getHealth } from './health/get_health';
export const EVENT_LOG_PROVIDER = 'alerting';
export const EVENT_LOG_ACTIONS = {
@@ -78,6 +88,7 @@ export interface PluginSetupContract {
export interface PluginStartContract {
listTypes: AlertTypeRegistry['list'];
getAlertsClientWithRequest(request: KibanaRequest): PublicMethodsOf;
+ getFrameworkHealth: () => Promise;
}
export interface AlertingPluginsSetup {
@@ -89,6 +100,7 @@ export interface AlertingPluginsSetup {
spaces?: SpacesPluginSetup;
usageCollection?: UsageCollectionSetup;
eventLog: IEventLogService;
+ statusService: StatusServiceSetup;
}
export interface AlertingPluginsStart {
actions: ActionsPluginStartContract;
@@ -99,6 +111,7 @@ export interface AlertingPluginsStart {
}
export class AlertingPlugin {
+ private readonly config: Promise;
private readonly logger: Logger;
private alertTypeRegistry?: AlertTypeRegistry;
private readonly taskRunnerFactory: TaskRunnerFactory;
@@ -115,6 +128,7 @@ export class AlertingPlugin {
private eventLogger?: IEventLogger;
constructor(initializerContext: PluginInitializerContext) {
+ this.config = initializerContext.config.create().pipe(first()).toPromise();
this.logger = initializerContext.logger.get('plugins', 'alerting');
this.taskRunnerFactory = new TaskRunnerFactory();
this.alertsClientFactory = new AlertsClientFactory();
@@ -186,6 +200,25 @@ export class AlertingPlugin {
});
}
+ core.getStartServices().then(async ([, startPlugins]) => {
+ core.status.set(
+ combineLatest([
+ core.status.derivedStatus$,
+ getHealthStatusStream(startPlugins.taskManager),
+ ]).pipe(
+ map(([derivedStatus, healthStatus]) => {
+ if (healthStatus.level > derivedStatus.level) {
+ return healthStatus as ServiceStatus;
+ } else {
+ return derivedStatus;
+ }
+ })
+ )
+ );
+ });
+
+ initializeAlertingHealth(this.logger, plugins.taskManager, core.getStartServices());
+
core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext(core));
// Routes
@@ -275,10 +308,13 @@ export class AlertingPlugin {
});
scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager);
+ scheduleAlertingHealthCheck(this.logger, this.config, plugins.taskManager);
return {
listTypes: alertTypeRegistry!.list.bind(this.alertTypeRegistry!),
getAlertsClientWithRequest,
+ getFrameworkHealth: async () =>
+ await getHealth(core.savedObjects.createInternalRepository(['alert'])),
};
}
@@ -293,6 +329,8 @@ export class AlertingPlugin {
return alertsClientFactory!.create(request, savedObjects);
},
listTypes: alertTypeRegistry!.list.bind(alertTypeRegistry!),
+ getFrameworkHealth: async () =>
+ await getHealth(savedObjects.createInternalRepository(['alert'])),
};
};
};
diff --git a/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts
index 3d13fc65ab260..b3f407b20c142 100644
--- a/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts
+++ b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts
@@ -14,7 +14,7 @@ import { identity } from 'lodash';
import type { MethodKeysOf } from '@kbn/utility-types';
import { httpServerMock } from '../../../../../src/core/server/mocks';
import { alertsClientMock, AlertsClientMock } from '../alerts_client.mock';
-import { AlertType } from '../../common';
+import { AlertsHealth, AlertType } from '../../common';
import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
export function mockHandlerArguments(
@@ -22,10 +22,13 @@ export function mockHandlerArguments(
alertsClient = alertsClientMock.create(),
listTypes: listTypesRes = [],
esClient = elasticsearchServiceMock.createLegacyClusterClient(),
+ getFrameworkHealth,
}: {
alertsClient?: AlertsClientMock;
listTypes?: AlertType[];
esClient?: jest.Mocked;
+ getFrameworkHealth?: jest.MockInstance, []> &
+ (() => Promise);
},
req: unknown,
res?: Array>
@@ -39,6 +42,7 @@ export function mockHandlerArguments(
getAlertsClient() {
return alertsClient || alertsClientMock.create();
},
+ getFrameworkHealth,
},
} as unknown) as RequestHandlerContext,
req as KibanaRequest,
diff --git a/x-pack/plugins/alerts/server/routes/health.test.ts b/x-pack/plugins/alerts/server/routes/health.test.ts
index ce782dbd631a5..d1967c6dd9bf8 100644
--- a/x-pack/plugins/alerts/server/routes/health.test.ts
+++ b/x-pack/plugins/alerts/server/routes/health.test.ts
@@ -11,13 +11,34 @@ import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
import { verifyApiAccess } from '../lib/license_api_access';
import { mockLicenseState } from '../lib/license_state.mock';
import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks';
+import { alertsClientMock } from '../alerts_client.mock';
+import { HealthStatus } from '../types';
+import { alertsMock } from '../mocks';
+const alertsClient = alertsClientMock.create();
jest.mock('../lib/license_api_access.ts', () => ({
verifyApiAccess: jest.fn(),
}));
+const alerting = alertsMock.createStart();
+
+const currentDate = new Date().toISOString();
beforeEach(() => {
jest.resetAllMocks();
+ alerting.getFrameworkHealth.mockResolvedValue({
+ decryptionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ executionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ readHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ });
});
describe('healthRoute', () => {
@@ -46,7 +67,7 @@ describe('healthRoute', () => {
const esClient = elasticsearchServiceMock.createLegacyClusterClient();
esClient.callAsInternalUser.mockReturnValue(Promise.resolve({}));
- const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']);
+ const [context, req, res] = mockHandlerArguments({ esClient, alertsClient }, {}, ['ok']);
await handler(context, req, res);
@@ -75,16 +96,32 @@ describe('healthRoute', () => {
const esClient = elasticsearchServiceMock.createLegacyClusterClient();
esClient.callAsInternalUser.mockReturnValue(Promise.resolve({}));
- const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']);
+ const [context, req, res] = mockHandlerArguments(
+ { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth },
+ {},
+ ['ok']
+ );
- expect(await handler(context, req, res)).toMatchInlineSnapshot(`
- Object {
- "body": Object {
- "hasPermanentEncryptionKey": false,
- "isSufficientlySecure": true,
+ expect(await handler(context, req, res)).toStrictEqual({
+ body: {
+ alertingFrameworkHeath: {
+ decryptionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ executionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ readHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
},
- }
- `);
+ hasPermanentEncryptionKey: false,
+ isSufficientlySecure: true,
+ },
+ });
});
it('evaluates missing security info from the usage api to mean that the security plugin is disbled', async () => {
@@ -99,16 +136,32 @@ describe('healthRoute', () => {
const esClient = elasticsearchServiceMock.createLegacyClusterClient();
esClient.callAsInternalUser.mockReturnValue(Promise.resolve({}));
- const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']);
+ const [context, req, res] = mockHandlerArguments(
+ { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth },
+ {},
+ ['ok']
+ );
- expect(await handler(context, req, res)).toMatchInlineSnapshot(`
- Object {
- "body": Object {
- "hasPermanentEncryptionKey": true,
- "isSufficientlySecure": true,
+ expect(await handler(context, req, res)).toStrictEqual({
+ body: {
+ alertingFrameworkHeath: {
+ decryptionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ executionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ readHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
},
- }
- `);
+ hasPermanentEncryptionKey: true,
+ isSufficientlySecure: true,
+ },
+ });
});
it('evaluates missing security http info from the usage api to mean that the security plugin is disbled', async () => {
@@ -123,16 +176,32 @@ describe('healthRoute', () => {
const esClient = elasticsearchServiceMock.createLegacyClusterClient();
esClient.callAsInternalUser.mockReturnValue(Promise.resolve({ security: {} }));
- const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']);
+ const [context, req, res] = mockHandlerArguments(
+ { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth },
+ {},
+ ['ok']
+ );
- expect(await handler(context, req, res)).toMatchInlineSnapshot(`
- Object {
- "body": Object {
- "hasPermanentEncryptionKey": true,
- "isSufficientlySecure": true,
+ expect(await handler(context, req, res)).toStrictEqual({
+ body: {
+ alertingFrameworkHeath: {
+ decryptionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ executionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ readHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
},
- }
- `);
+ hasPermanentEncryptionKey: true,
+ isSufficientlySecure: true,
+ },
+ });
});
it('evaluates security enabled, and missing ssl info from the usage api to mean that the user cannot generate keys', async () => {
@@ -147,16 +216,32 @@ describe('healthRoute', () => {
const esClient = elasticsearchServiceMock.createLegacyClusterClient();
esClient.callAsInternalUser.mockReturnValue(Promise.resolve({ security: { enabled: true } }));
- const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']);
+ const [context, req, res] = mockHandlerArguments(
+ { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth },
+ {},
+ ['ok']
+ );
- expect(await handler(context, req, res)).toMatchInlineSnapshot(`
- Object {
- "body": Object {
- "hasPermanentEncryptionKey": true,
- "isSufficientlySecure": false,
+ expect(await handler(context, req, res)).toStrictEqual({
+ body: {
+ alertingFrameworkHeath: {
+ decryptionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ executionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ readHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
},
- }
- `);
+ hasPermanentEncryptionKey: true,
+ isSufficientlySecure: false,
+ },
+ });
});
it('evaluates security enabled, SSL info present but missing http info from the usage api to mean that the user cannot generate keys', async () => {
@@ -173,16 +258,32 @@ describe('healthRoute', () => {
Promise.resolve({ security: { enabled: true, ssl: {} } })
);
- const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']);
+ const [context, req, res] = mockHandlerArguments(
+ { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth },
+ {},
+ ['ok']
+ );
- expect(await handler(context, req, res)).toMatchInlineSnapshot(`
- Object {
- "body": Object {
- "hasPermanentEncryptionKey": true,
- "isSufficientlySecure": false,
+ expect(await handler(context, req, res)).toStrictEqual({
+ body: {
+ alertingFrameworkHeath: {
+ decryptionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ executionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ readHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
},
- }
- `);
+ hasPermanentEncryptionKey: true,
+ isSufficientlySecure: false,
+ },
+ });
});
it('evaluates security and tls enabled to mean that the user can generate keys', async () => {
@@ -199,15 +300,31 @@ describe('healthRoute', () => {
Promise.resolve({ security: { enabled: true, ssl: { http: { enabled: true } } } })
);
- const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']);
+ const [context, req, res] = mockHandlerArguments(
+ { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth },
+ {},
+ ['ok']
+ );
- expect(await handler(context, req, res)).toMatchInlineSnapshot(`
- Object {
- "body": Object {
- "hasPermanentEncryptionKey": true,
- "isSufficientlySecure": true,
+ expect(await handler(context, req, res)).toStrictEqual({
+ body: {
+ alertingFrameworkHeath: {
+ decryptionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ executionHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
+ readHealth: {
+ status: HealthStatus.OK,
+ timestamp: currentDate,
+ },
},
- }
- `);
+ hasPermanentEncryptionKey: true,
+ isSufficientlySecure: true,
+ },
+ });
});
});
diff --git a/x-pack/plugins/alerts/server/routes/health.ts b/x-pack/plugins/alerts/server/routes/health.ts
index b66e28b24e8a7..bfd5b1e272287 100644
--- a/x-pack/plugins/alerts/server/routes/health.ts
+++ b/x-pack/plugins/alerts/server/routes/health.ts
@@ -43,6 +43,9 @@ export function healthRoute(
res: KibanaResponseFactory
): Promise {
verifyApiAccess(licenseState);
+ if (!context.alerting) {
+ return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
+ }
try {
const {
security: {
@@ -57,9 +60,12 @@ export function healthRoute(
path: '/_xpack/usage',
});
+ const alertingFrameworkHeath = await context.alerting.getFrameworkHealth();
+
const frameworkHealth: AlertingFrameworkHealth = {
isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled),
hasPermanentEncryptionKey: !encryptedSavedObjects.usingEphemeralEncryptionKey,
+ alertingFrameworkHeath,
};
return res.ok({
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 86e78dea66a09..4d0d69010914e 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
@@ -292,6 +292,7 @@ describe('Task Runner', () => {
kibana: {
alerting: {
instance_id: '1',
+ action_group_id: 'default',
},
saved_objects: [
{
@@ -302,7 +303,7 @@ describe('Task Runner', () => {
},
],
},
- message: "test:1: 'alert-name' active instance: '1'",
+ message: "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'",
});
expect(eventLogger.logEvent).toHaveBeenCalledWith({
event: {
@@ -424,6 +425,7 @@ describe('Task Runner', () => {
},
"kibana": Object {
"alerting": Object {
+ "action_group_id": undefined,
"instance_id": "1",
},
"saved_objects": Array [
@@ -445,6 +447,7 @@ describe('Task Runner', () => {
},
"kibana": Object {
"alerting": Object {
+ "action_group_id": "default",
"instance_id": "1",
},
"saved_objects": Array [
@@ -456,7 +459,7 @@ describe('Task Runner', () => {
},
],
},
- "message": "test:1: 'alert-name' active instance: '1'",
+ "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'",
},
],
Array [
@@ -565,6 +568,7 @@ describe('Task Runner', () => {
},
"kibana": Object {
"alerting": Object {
+ "action_group_id": undefined,
"instance_id": "2",
},
"saved_objects": Array [
@@ -586,6 +590,7 @@ describe('Task Runner', () => {
},
"kibana": Object {
"alerting": Object {
+ "action_group_id": "default",
"instance_id": "1",
},
"saved_objects": Array [
@@ -597,7 +602,7 @@ describe('Task Runner', () => {
},
],
},
- "message": "test:1: 'alert-name' active instance: '1'",
+ "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'",
},
],
]
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 2611ba766173b..86bf7006e8d09 100644
--- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts
+++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
-import { pickBy, mapValues, without } from 'lodash';
+import { Dictionary, pickBy, mapValues, without } from 'lodash';
import { Logger, KibanaRequest } from '../../../../../src/core/server';
import { TaskRunnerContext } from './task_runner_factory';
import { ConcreteTaskInstance, throwUnrecoverableError } from '../../../task_manager/server';
@@ -28,6 +28,7 @@ import {
AlertExecutorOptions,
SanitizedAlert,
AlertExecutionStatus,
+ AlertExecutionStatusErrorReasons,
} from '../types';
import { promiseResult, map, Resultable, asOk, asErr, resolveErr } from '../lib/result_type';
import { taskInstanceToAlertTaskInstance } from './alert_task_instance';
@@ -211,7 +212,7 @@ export class TaskRunner {
event.event = event.event || {};
event.event.outcome = 'failure';
eventLogger.logEvent(event);
- throw new ErrorWithReason('execute', err);
+ throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Execute, err);
}
eventLogger.stopTiming(event);
@@ -224,11 +225,10 @@ export class TaskRunner {
const instancesWithScheduledActions = pickBy(alertInstances, (alertInstance: AlertInstance) =>
alertInstance.hasScheduledActions()
);
- const currentAlertInstanceIds = Object.keys(instancesWithScheduledActions);
generateNewAndResolvedInstanceEvents({
eventLogger,
originalAlertInstanceIds,
- currentAlertInstanceIds,
+ currentAlertInstances: instancesWithScheduledActions,
alertId,
alertLabel,
namespace,
@@ -289,7 +289,7 @@ export class TaskRunner {
try {
apiKey = await this.getApiKeyForAlertPermissions(alertId, spaceId);
} catch (err) {
- throw new ErrorWithReason('decrypt', err);
+ throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Decrypt, err);
}
const [services, alertsClient] = this.getServicesWithSpaceLevelPermissions(spaceId, apiKey);
@@ -299,7 +299,7 @@ export class TaskRunner {
try {
alert = await alertsClient.get({ id: alertId });
} catch (err) {
- throw new ErrorWithReason('read', err);
+ throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Read, err);
}
return {
@@ -382,7 +382,7 @@ export class TaskRunner {
interface GenerateNewAndResolvedInstanceEventsParams {
eventLogger: IEventLogger;
originalAlertInstanceIds: string[];
- currentAlertInstanceIds: string[];
+ currentAlertInstances: Dictionary;
alertId: string;
alertLabel: string;
namespace: string | undefined;
@@ -393,9 +393,10 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst
eventLogger,
alertId,
namespace,
- currentAlertInstanceIds,
+ currentAlertInstances,
originalAlertInstanceIds,
} = params;
+ const currentAlertInstanceIds = Object.keys(currentAlertInstances);
const newIds = without(currentAlertInstanceIds, ...originalAlertInstanceIds);
const resolvedIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds);
@@ -411,11 +412,12 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst
}
for (const id of currentAlertInstanceIds) {
- const message = `${params.alertLabel} active instance: '${id}'`;
- logInstanceEvent(id, EVENT_LOG_ACTIONS.activeInstance, message);
+ const actionGroup = currentAlertInstances[id].getScheduledActionOptions()?.actionGroup;
+ const message = `${params.alertLabel} active instance: '${id}' in actionGroup: '${actionGroup}'`;
+ logInstanceEvent(id, EVENT_LOG_ACTIONS.activeInstance, message, actionGroup);
}
- function logInstanceEvent(instanceId: string, action: string, message: string) {
+ function logInstanceEvent(instanceId: string, action: string, message: string, group?: string) {
const event: IEvent = {
event: {
action,
@@ -423,6 +425,7 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst
kibana: {
alerting: {
instance_id: instanceId,
+ action_group_id: group,
},
saved_objects: [
{
diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts
index 42eef9bba10e5..9226461f6e30a 100644
--- a/x-pack/plugins/alerts/server/types.ts
+++ b/x-pack/plugins/alerts/server/types.ts
@@ -27,6 +27,7 @@ import {
AlertInstanceState,
AlertExecutionStatuses,
AlertExecutionStatusErrorReasons,
+ AlertsHealth,
} from '../common';
export type WithoutQueryAndParams = Pick>;
@@ -39,6 +40,7 @@ declare module 'src/core/server' {
alerting?: {
getAlertsClient: () => AlertsClient;
listTypes: AlertTypeRegistry['list'];
+ getFrameworkHealth: () => Promise;
};
}
}
@@ -172,4 +174,10 @@ export interface AlertingPlugin {
start: PluginStartContract;
}
+export interface AlertsConfigType {
+ healthCheck: {
+ interval: string;
+ };
+}
+
export type AlertTypeRegistry = PublicMethodsOf;
diff --git a/x-pack/plugins/apm/e2e/cypress/integration/apm.feature b/x-pack/plugins/apm/e2e/cypress/integration/apm.feature
index 285615108266b..494a6b5fadb5b 100644
--- a/x-pack/plugins/apm/e2e/cypress/integration/apm.feature
+++ b/x-pack/plugins/apm/e2e/cypress/integration/apm.feature
@@ -3,5 +3,4 @@ Feature: APM
Scenario: Transaction duration charts
Given a user browses the APM UI application
When the user inspects the opbeans-node service
- Then should redirect to correct path with correct params
- And should have correct y-axis ticks
+ Then should redirect to correct path with correct params
\ No newline at end of file
diff --git a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js
index 72b49bb85b7a5..0ecda7a113de7 100644
--- a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js
+++ b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js
@@ -1,3 +1,3 @@
module.exports = {
- __version: '5.5.0',
-};
+ "__version": "5.4.0"
+}
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts
index 50c620dca9ddf..42c2bc7ffd318 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts
@@ -29,16 +29,3 @@ Then(`should redirect to correct path with correct params`, () => {
cy.url().should('contain', `/app/apm/services/opbeans-node/transactions`);
cy.url().should('contain', `transactionType=request`);
});
-
-Then(`should have correct y-axis ticks`, () => {
- const yAxisTick =
- '[data-cy=transaction-duration-charts] .rv-xy-plot__axis--vertical .rv-xy-plot__axis__tick__text';
-
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
-
- // literal assertions because snapshot() doesn't retry
- cy.get(yAxisTick).eq(2).should('have.text', '55 ms');
- cy.get(yAxisTick).eq(1).should('have.text', '28 ms');
- cy.get(yAxisTick).eq(0).should('have.text', '0 ms');
-});
diff --git a/x-pack/plugins/apm/e2e/package.json b/x-pack/plugins/apm/e2e/package.json
deleted file mode 100644
index 5839f4d58537c..0000000000000
--- a/x-pack/plugins/apm/e2e/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "name": "apm-cypress",
- "version": "1.0.0",
- "main": "index.js",
- "license": "MIT",
- "scripts": {
- "cypress:open": "../../../../node_modules/.bin/cypress open",
- "cypress:run": "../../../../node_modules/.bin/cypress run --spec **/*.feature"
- }
-}
\ No newline at end of file
diff --git a/x-pack/plugins/apm/e2e/run-e2e.sh b/x-pack/plugins/apm/e2e/run-e2e.sh
index 6cdae93aec63b..85ab67bbf9a10 100755
--- a/x-pack/plugins/apm/e2e/run-e2e.sh
+++ b/x-pack/plugins/apm/e2e/run-e2e.sh
@@ -20,6 +20,8 @@ normal=$(tput sgr0)
E2E_DIR="${0%/*}"
TMP_DIR="tmp"
APM_IT_DIR="tmp/apm-integration-testing"
+WAIT_ON_BIN="../../../../node_modules/.bin/wait-on"
+CYPRESS_BIN="../../../../node_modules/.bin/cypress"
cd ${E2E_DIR}
@@ -92,14 +94,6 @@ if [ $? -ne 0 ]; then
exit 1
fi
-#
-# Cypress
-##################################################
-echo "" # newline
-echo "${bold}Cypress (logs: ${E2E_DIR}${TMP_DIR}/e2e-yarn.log)${normal}"
-echo "Installing cypress dependencies "
-yarn &> ${TMP_DIR}/e2e-yarn.log
-
#
# Static mock data
##################################################
@@ -148,7 +142,7 @@ fi
echo "" # newline
echo "${bold}Waiting for Kibana to start...${normal}"
echo "Note: you need to start Kibana manually. Find the instructions at the top."
-yarn wait-on -i 500 -w 500 http-get://admin:changeme@localhost:$KIBANA_PORT/api/status > /dev/null
+$WAIT_ON_BIN -i 500 -w 500 http-get://admin:changeme@localhost:$KIBANA_PORT/api/status > /dev/null
## Workaround to wait for the http server running
## See: https://github.com/elastic/kibana/issues/66326
@@ -165,7 +159,7 @@ echo "✅ Setup completed successfully. Running tests..."
#
# run cypress tests
##################################################
-yarn cypress run --config pageLoadTimeout=100000,watchForFileChanges=true
+$CYPRESS_BIN run --config pageLoadTimeout=100000,watchForFileChanges=true
e2e_status=$?
#
@@ -173,7 +167,7 @@ e2e_status=$?
##################################################
echo "${bold}If you want to run the test interactively, run:${normal}"
echo "" # newline
-echo "cd ${E2E_DIR} && yarn cypress open --config pageLoadTimeout=100000,watchForFileChanges=true"
+echo "cd ${E2E_DIR} && ${CYPRESS_BIN} open --config pageLoadTimeout=100000,watchForFileChanges=true"
# Report the e2e status at the very end
if [ $e2e_status -ne 0 ]; then
diff --git a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts
index 0eeb31927b2f5..988e335af5b7c 100644
--- a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts
+++ b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts
@@ -22,6 +22,9 @@ export function registerApmAlerts(
'Alert when the number of errors in a service exceeds a defined threshold.',
}),
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`;
+ },
alertParamsExpression: lazy(() => import('./ErrorCountAlertTrigger')),
validate: () => ({
errors: [],
@@ -53,6 +56,9 @@ export function registerApmAlerts(
}
),
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`;
+ },
alertParamsExpression: lazy(
() => import('./TransactionDurationAlertTrigger')
),
@@ -87,6 +93,9 @@ export function registerApmAlerts(
}
),
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`;
+ },
alertParamsExpression: lazy(
() => import('./TransactionErrorRateAlertTrigger')
),
@@ -121,6 +130,9 @@ export function registerApmAlerts(
}
),
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`;
+ },
alertParamsExpression: lazy(
() => import('./TransactionDurationAnomalyAlertTrigger')
),
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
index e17dd9a9eb038..a17bf7e93e466 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
@@ -4,31 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import {
+ Axis,
+ Chart,
+ HistogramBarSeries,
+ niceTimeFormatter,
+ Position,
+ ScaleType,
+ Settings,
+ SettingsSpec,
+ TooltipValue,
+} from '@elastic/charts';
import { EuiTitle } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
-import numeral from '@elastic/numeral';
-import { i18n } from '@kbn/i18n';
import d3 from 'd3';
-import { scaleUtc } from 'd3-scale';
-import { mean } from 'lodash';
import React from 'react';
import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters';
-import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs';
-// @ts-expect-error
-import Histogram from '../../../shared/charts/Histogram';
-import { EmptyMessage } from '../../../shared/EmptyMessage';
-
-interface IBucket {
- key: number;
- count: number | undefined;
-}
-
-// TODO: cleanup duplication of this in distribution/get_distribution.ts (ErrorDistributionAPIResponse) and transactions/distribution/index.ts (TransactionDistributionAPIResponse)
-interface IDistribution {
- noHits: boolean;
- buckets: IBucket[];
- bucketSize: number;
-}
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import type { ErrorDistributionAPIResponse } from '../../../../../server/lib/errors/distribution/get_distribution';
+import { useTheme } from '../../../../hooks/useTheme';
interface FormattedBucket {
x0: number;
@@ -37,13 +30,9 @@ interface FormattedBucket {
}
export function getFormattedBuckets(
- buckets: IBucket[],
+ buckets: ErrorDistributionAPIResponse['buckets'],
bucketSize: number
-): FormattedBucket[] | null {
- if (!buckets) {
- return null;
- }
-
+): FormattedBucket[] {
return buckets.map(({ count, key }) => {
return {
x0: key,
@@ -54,76 +43,66 @@ export function getFormattedBuckets(
}
interface Props {
- distribution: IDistribution;
+ distribution: ErrorDistributionAPIResponse;
title: React.ReactNode;
}
-const tooltipHeader = (bucket: FormattedBucket) =>
- asRelativeDateTimeRange(bucket.x0, bucket.x);
-
export function ErrorDistribution({ distribution, title }: Props) {
+ const theme = useTheme();
const buckets = getFormattedBuckets(
distribution.buckets,
distribution.bucketSize
);
- if (!buckets) {
- return (
-
- );
- }
-
- const averageValue = mean(buckets.map((bucket) => bucket.y)) || 0;
const xMin = d3.min(buckets, (d) => d.x0);
- const xMax = d3.max(buckets, (d) => d.x);
- const tickFormat = scaleUtc().domain([xMin, xMax]).tickFormat();
+ const xMax = d3.max(buckets, (d) => d.x0);
+
+ const xFormatter = niceTimeFormatter([xMin, xMax]);
+
+ const tooltipProps: SettingsSpec['tooltip'] = {
+ headerFormatter: (tooltip: TooltipValue) => {
+ const serie = buckets.find((bucket) => bucket.x0 === tooltip.value);
+ if (serie) {
+ return asRelativeDateTimeRange(serie.x0, serie.x);
+ }
+ return `${tooltip.value}`;
+ },
+ };
return (
{title}
-
bucket.x}
- xType="time-utc"
- formatX={(value: Date) => {
- const time = value.getTime();
- return tickFormat(new Date(time - getTimezoneOffsetInMs(time)));
- }}
- buckets={buckets}
- bucketSize={distribution.bucketSize}
- formatYShort={(value: number) =>
- i18n.translate('xpack.apm.errorGroupDetails.occurrencesShortLabel', {
- defaultMessage: '{occCount} occ.',
- values: { occCount: value },
- })
- }
- formatYLong={(value: number) =>
- i18n.translate('xpack.apm.errorGroupDetails.occurrencesLongLabel', {
- defaultMessage:
- '{occCount} {occCount, plural, one {occurrence} other {occurrences}}',
- values: { occCount: value },
- })
- }
- legends={[
- {
- color: theme.euiColorVis1,
- // 0a abbreviates large whole numbers with metric prefixes like: 1000 = 1k, 32000 = 32k, 1000000 = 1m
- legendValue: numeral(averageValue).format('0a'),
- title: i18n.translate('xpack.apm.errorGroupDetails.avgLabel', {
- defaultMessage: 'Avg.',
- }),
- legendClickDisabled: true,
- },
- ]}
- />
+
);
}
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 b5a558621e9ca..1f34a0cef1ccf 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
@@ -826,7 +826,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
query={
Object {
"end": "2018-01-10T10:06:41.050Z",
- "kuery": "error.exception.type:AssertionError",
+ "kuery": "error.exception.type:\\"AssertionError\\"",
"page": 0,
"serviceName": "opbeans-python",
"start": "2018-01-10T09:51:41.050Z",
@@ -838,12 +838,12 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
>
@@ -1065,7 +1065,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
query={
Object {
"end": "2018-01-10T10:06:41.050Z",
- "kuery": "error.exception.type:AssertionError",
+ "kuery": "error.exception.type:\\"AssertionError\\"",
"page": 0,
"serviceName": "opbeans-python",
"start": "2018-01-10T09:51:41.050Z",
@@ -1077,12 +1077,12 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
>
@@ -1304,7 +1304,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
query={
Object {
"end": "2018-01-10T10:06:41.050Z",
- "kuery": "error.exception.type:AssertionError",
+ "kuery": "error.exception.type:\\"AssertionError\\"",
"page": 0,
"serviceName": "opbeans-python",
"start": "2018-01-10T09:51:41.050Z",
@@ -1316,12 +1316,12 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
>
@@ -1543,7 +1543,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
query={
Object {
"end": "2018-01-10T10:06:41.050Z",
- "kuery": "error.exception.type:AssertionError",
+ "kuery": "error.exception.type:\\"AssertionError\\"",
"page": 0,
"serviceName": "opbeans-python",
"start": "2018-01-10T09:51:41.050Z",
@@ -1555,12 +1555,12 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
>
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx
index 33105189f9c3e..e1f6239112555 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx
@@ -107,7 +107,7 @@ function ErrorGroupList({ items, serviceName }: Props) {
query={
{
...urlParams,
- kuery: `error.exception.type:${type}`,
+ kuery: `error.exception.type:"${type}"`,
} as APMQueryParams
}
>
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx
index efc52e7cb426a..7c21079885334 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx
@@ -44,12 +44,12 @@ export function MainFilters() {
serviceNames={data ?? []}
/>
-
-
-
+
+
+
>
);
}
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx
index cf419f6edffc0..b70621b1e4cbc 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx
@@ -65,7 +65,7 @@ function ServiceNameFilter({ loading, serviceNames }: Props) {
prepend={i18n.translate(
'xpack.apm.ux.localFilters.titles.webApplication',
{
- defaultMessage: 'Web Application',
+ defaultMessage: 'Web application',
}
)}
isLoading={loading}
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx
index 67125d41635a9..bf1bda793179f 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx
@@ -4,22 +4,37 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import {
+ Axis,
+ Chart,
+ ElementClickListener,
+ GeometryValue,
+ HistogramBarSeries,
+ Position,
+ RectAnnotation,
+ ScaleType,
+ Settings,
+ SettingsSpec,
+ TooltipValue,
+ XYChartSeriesIdentifier,
+} from '@elastic/charts';
import { EuiIconTip, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import d3 from 'd3';
import { isEmpty } from 'lodash';
import React, { useCallback } from 'react';
import { ValuesType } from 'utility-types';
+import { useTheme } from '../../../../../../observability/public';
import { getDurationFormatter } from '../../../../../common/utils/formatters';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution';
+import type { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets';
+import type { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets';
import { IUrlParams } from '../../../../context/UrlParamsContext/types';
-// @ts-expect-error
-import Histogram from '../../../shared/charts/Histogram';
+import { FETCH_STATUS } from '../../../../hooks/useFetcher';
+import { unit } from '../../../../style/variables';
+import { ChartContainer } from '../../../shared/charts/chart_container';
import { EmptyMessage } from '../../../shared/EmptyMessage';
-import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt';
interface IChartPoint {
x0: number;
@@ -31,10 +46,10 @@ interface IChartPoint {
}
export function getFormattedBuckets(
- buckets: DistributionBucket[],
- bucketSize: number
+ buckets?: DistributionBucket[],
+ bucketSize?: number
) {
- if (!buckets) {
+ if (!buckets || !bucketSize) {
return [];
}
@@ -74,7 +89,7 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => {
'xpack.apm.transactionDetails.transactionsDurationDistributionChart.requestTypeUnitLongLabel',
{
defaultMessage:
- '{transCount, plural, =0 {# request} one {# request} other {# requests}}',
+ '{transCount, plural, =0 {request} one {request} other {requests}}',
values: {
transCount: t,
},
@@ -84,7 +99,7 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => {
'xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel',
{
defaultMessage:
- '{transCount, plural, =0 {# transaction} one {# transaction} other {# transactions}}',
+ '{transCount, plural, =0 {transaction} one {transaction} other {transactions}}',
values: {
transCount: t,
},
@@ -95,21 +110,21 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => {
interface Props {
distribution?: TransactionDistributionAPIResponse;
urlParams: IUrlParams;
- isLoading: boolean;
+ fetchStatus: FETCH_STATUS;
bucketIndex: number;
onBucketClick: (
bucket: ValuesType
) => void;
}
-export function TransactionDistribution(props: Props) {
- const {
- distribution,
- urlParams: { transactionType },
- isLoading,
- bucketIndex,
- onBucketClick,
- } = props;
+export function TransactionDistribution({
+ distribution,
+ urlParams: { transactionType },
+ fetchStatus,
+ bucketIndex,
+ onBucketClick,
+}: Props) {
+ const theme = useTheme();
/* eslint-disable-next-line react-hooks/exhaustive-deps */
const formatYShort = useCallback(getFormatYShort(transactionType), [
@@ -122,12 +137,10 @@ export function TransactionDistribution(props: Props) {
]);
// no data in response
- if (!distribution || distribution.noHits) {
- // only show loading state if there is no data - else show stale data until new data has loaded
- if (isLoading) {
- return ;
- }
-
+ if (
+ (!distribution || distribution.noHits) &&
+ fetchStatus !== FETCH_STATUS.LOADING
+ ) {
return (
{
- return bucket.key === chartPoint.x0;
- });
-
- return clickedBucket;
- }
-
const buckets = getFormattedBuckets(
- distribution.buckets,
- distribution.bucketSize
+ distribution?.buckets,
+ distribution?.bucketSize
);
- const xMax = d3.max(buckets, (d) => d.x) || 0;
+ const xMin = d3.min(buckets, (d) => d.x0) || 0;
+ const xMax = d3.max(buckets, (d) => d.x0) || 0;
const timeFormatter = getDurationFormatter(xMax);
+ const tooltipProps: SettingsSpec['tooltip'] = {
+ headerFormatter: (tooltip: TooltipValue) => {
+ const serie = buckets.find((bucket) => bucket.x0 === tooltip.value);
+ if (serie) {
+ const xFormatted = timeFormatter(serie.x);
+ const x0Formatted = timeFormatter(serie.x0);
+ return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`;
+ }
+ return `${timeFormatter(tooltip.value)}`;
+ },
+ };
+
+ const onBarClick: ElementClickListener = (elements) => {
+ const chartPoint = elements[0][0] as GeometryValue;
+ const clickedBucket = distribution?.buckets.find((bucket) => {
+ return bucket.key === chartPoint.x;
+ });
+ if (clickedBucket) {
+ onBucketClick(clickedBucket);
+ }
+ };
+
+ const selectedBucket = buckets[bucketIndex];
+
return (
@@ -181,42 +211,66 @@ export function TransactionDistribution(props: Props) {
/>
-
-
{
- const clickedBucket = getBucketFromChartPoint(chartPoint);
-
- if (clickedBucket) {
- onBucketClick(clickedBucket);
- }
- }}
- formatX={(time: number) => timeFormatter(time).formatted}
- formatYShort={formatYShort}
- formatYLong={formatYLong}
- verticalLineHover={(point: IChartPoint) =>
- isEmpty(getBucketFromChartPoint(point)?.samples)
- }
- backgroundHover={(point: IChartPoint) =>
- !isEmpty(getBucketFromChartPoint(point)?.samples)
- }
- tooltipHeader={(point: IChartPoint) => {
- const xFormatted = timeFormatter(point.x);
- const x0Formatted = timeFormatter(point.x0);
- return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`;
- }}
- tooltipFooter={(point: IChartPoint) =>
- isEmpty(getBucketFromChartPoint(point)?.samples) &&
- i18n.translate(
- 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip',
- {
- defaultMessage: 'No sample available for this bucket',
- }
- )
- }
- />
+
+
+
+ {selectedBucket && (
+
+ )}
+ timeFormatter(time).formatted}
+ />
+ formatYShort(value)}
+ />
+ value}
+ minBarHeight={2}
+ id="transactionDurationDistribution"
+ name={(series: XYChartSeriesIdentifier) => {
+ const bucketCount = series.splitAccessors.get(
+ series.yAccessor
+ ) as number;
+ return formatYLong(bucketCount);
+ }}
+ splitSeriesAccessors={['y']}
+ xScaleType={ScaleType.Linear}
+ yScaleType={ScaleType.Linear}
+ xAccessor="x0"
+ yAccessors={['y']}
+ data={buckets}
+ color={theme.eui.euiColorVis1}
+ />
+
+
);
}
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx
index efdd7b1f34221..e4c36b028e55c 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx
@@ -52,7 +52,11 @@ export function TransactionDetails({
status: distributionStatus,
} = useTransactionDistribution(urlParams);
- const { data: transactionChartsData } = useTransactionCharts();
+ const {
+ data: transactionChartsData,
+ status: transactionChartsStatus,
+ } = useTransactionCharts();
+
const { waterfall, exceedsMax, status: waterfallStatus } = useWaterfall(
urlParams
);
@@ -121,6 +125,7 @@ export function TransactionDetails({
@@ -131,7 +136,7 @@ export function TransactionDetails({
{
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx
index b7d1b93600a73..c530a7e1489ad 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx
@@ -4,12 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import {
- fireEvent,
- getByText,
- queryByLabelText,
- render,
-} from '@testing-library/react';
+import { fireEvent, getByText, queryByLabelText } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { CoreStart } from 'kibana/public';
import React from 'react';
@@ -20,7 +15,10 @@ import { UrlParamsProvider } from '../../../context/UrlParamsContext';
import { IUrlParams } from '../../../context/UrlParamsContext/types';
import * as useFetcherHook from '../../../hooks/useFetcher';
import * as useServiceTransactionTypesHook from '../../../hooks/useServiceTransactionTypes';
-import { disableConsoleWarning } from '../../../utils/testHelpers';
+import {
+ disableConsoleWarning,
+ renderWithTheme,
+} from '../../../utils/testHelpers';
import { fromQuery } from '../../shared/Links/url_helpers';
import { TransactionOverview } from './';
@@ -54,7 +52,7 @@ function setup({
jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any);
- return render(
+ return renderWithTheme(
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx
index 5444d2d521f37..df9e673ed4847 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx
@@ -22,7 +22,7 @@ import React, { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { useTrackPageview } from '../../../../../observability/public';
import { Projection } from '../../../../common/projections';
-import { LegacyChartsSyncContextProvider as ChartsSyncContextProvider } from '../../../context/charts_sync_context';
+import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types';
import { IUrlParams } from '../../../context/UrlParamsContext/types';
import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes';
import { useTransactionCharts } from '../../../hooks/useTransactionCharts';
@@ -33,11 +33,10 @@ import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink';
import { fromQuery, toQuery } from '../../shared/Links/url_helpers';
import { LocalUIFilters } from '../../shared/LocalUIFilters';
import { TransactionTypeFilter } from '../../shared/LocalUIFilters/TransactionTypeFilter';
+import { Correlations } from '../Correlations';
import { TransactionList } from './TransactionList';
import { useRedirect } from './useRedirect';
-import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types';
import { UserExperienceCallout } from './user_experience_callout';
-import { Correlations } from '../Correlations';
function getRedirectLocation({
urlParams,
@@ -83,7 +82,10 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) {
})
);
- const { data: transactionCharts } = useTransactionCharts();
+ const {
+ data: transactionCharts,
+ status: transactionChartsStatus,
+ } = useTransactionCharts();
useTrackPageview({ app: 'apm', path: 'transaction_overview' });
useTrackPageview({ app: 'apm', path: 'transaction_overview', delay: 15000 });
@@ -135,12 +137,11 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) {
>
)}
-
-
-
+
@@ -190,7 +191,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) {
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx
index 342152b572f1e..016ee3daf6b51 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx
@@ -11,7 +11,7 @@ import styled from 'styled-components';
import { useTrackPageview } from '../../../../../observability/public';
import { isRumAgentName } from '../../../../common/agent_name';
import { ChartsSyncContextProvider } from '../../../context/charts_sync_context';
-import { ErroneousTransactionsRateChart } from '../../shared/charts/erroneous_transactions_rate_chart';
+import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart';
import { ErrorOverviewLink } from '../../shared/Links/apm/ErrorOverviewLink';
import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink';
import { TransactionOverviewLink } from '../../shared/Links/apm/TransactionOverviewLink';
@@ -125,19 +125,7 @@ export function ServiceOverview({
{!isRumAgentName(agentName) && (
-
-
-
- {i18n.translate(
- 'xpack.apm.serviceOverview.errorRateChartTitle',
- {
- defaultMessage: 'Error rate',
- }
- )}
-
-
-
-
+
)}
diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx
index c3ecaa1b053b6..e6e40e44bad38 100644
--- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx
@@ -76,7 +76,7 @@ export function EnvironmentFilter() {
return (
) => {
- return asPercent(y ?? 0, 1);
-};
+export function TransactionBreakdownGraph({ fetchStatus, timeseries }: Props) {
+ const history = useHistory();
+ const chartRef = React.createRef();
+ const { event, setEvent } = useChartsSync2();
+ const { urlParams } = useUrlParams();
+ const { start, end } = urlParams;
-const formatTooltipValue = (coordinate: Coordinate) => {
- return isValidCoordinateValue(coordinate.y)
- ? asPercent(coordinate.y, 1)
- : NOT_AVAILABLE_LABEL;
-};
+ useEffect(() => {
+ if (event.chartId !== 'timeSpentBySpan' && chartRef.current) {
+ chartRef.current.dispatchExternalPointerEvent(event);
+ }
+ }, [chartRef, event]);
-function TransactionBreakdownGraph({ timeseries, noHits }: Props) {
- const { urlParams } = useUrlParams();
- const { rangeFrom, rangeTo } = urlParams;
- const trackApmEvent = useUiTracker({ app: 'apm' });
- const handleHover = useMemo(
- () =>
- throttle(() => trackApmEvent({ metric: 'hover_breakdown_chart' }), 60000),
- [trackApmEvent]
- );
+ const min = moment.utc(start).valueOf();
+ const max = moment.utc(end).valueOf();
- const emptySeries =
- rangeFrom && rangeTo
- ? getEmptySeries(
- new Date(rangeFrom).getTime(),
- new Date(rangeTo).getTime()
- )
- : [];
+ const xFormatter = niceTimeFormatter([min, max]);
return (
-
+
+
+ onBrushEnd({ x, history })}
+ showLegend
+ showLegendExtra
+ legendPosition={Position.Bottom}
+ xDomain={{ min, max }}
+ flatLegend
+ onPointerUpdate={(currEvent: any) => {
+ setEvent(currEvent);
+ }}
+ externalPointerEvents={{
+ tooltip: { visible: true, placement: Placement.Bottom },
+ }}
+ />
+
+ asPercent(y ?? 0, 1)}
+ />
+
+
+
+ {timeseries?.length ? (
+ timeseries.map((serie) => {
+ return (
+
+ );
+ })
+ ) : (
+ // When timeseries is empty, loads an AreaSeries chart to show the default empty message.
+
+ )}
+
+
);
}
-
-export { TransactionBreakdownGraph };
diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx
index 55826497ca385..9b0c041aaf7b5 100644
--- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx
@@ -5,16 +5,13 @@
*/
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { isEmpty } from 'lodash';
import React from 'react';
-import { FETCH_STATUS } from '../../../hooks/useFetcher';
import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown';
import { TransactionBreakdownGraph } from './TransactionBreakdownGraph';
function TransactionBreakdown() {
const { data, status } = useTransactionBreakdown();
const { timeseries } = data;
- const noHits = isEmpty(timeseries) && status === FETCH_STATUS.SUCCESS;
return (
@@ -29,7 +26,10 @@ function TransactionBreakdown() {
-
+
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js
deleted file mode 100644
index ca85ee961f5d8..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js
+++ /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 React from 'react';
-import PropTypes from 'prop-types';
-
-function SingleRect({ innerHeight, marginTop, style, x, width }) {
- return (
-
- );
-}
-
-SingleRect.requiresSVG = true;
-SingleRect.propTypes = {
- x: PropTypes.number.isRequired,
-};
-
-export default SingleRect;
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js
deleted file mode 100644
index 03fd039a3401e..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js
+++ /dev/null
@@ -1,119 +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 React from 'react';
-
-import d3 from 'd3';
-import { HistogramInner } from '../index';
-import response from './response.json';
-import {
- disableConsoleWarning,
- toJson,
- mountWithTheme,
-} from '../../../../../utils/testHelpers';
-import { getFormattedBuckets } from '../../../../app/TransactionDetails/Distribution/index';
-import {
- asInteger,
- getDurationFormatter,
-} from '../../../../../../common/utils/formatters';
-
-describe('Histogram', () => {
- let mockConsole;
- let wrapper;
-
- const onClick = jest.fn();
-
- beforeAll(() => {
- mockConsole = disableConsoleWarning('Warning: componentWillReceiveProps');
- });
-
- afterAll(() => {
- mockConsole.mockRestore();
- });
-
- beforeEach(() => {
- const buckets = getFormattedBuckets(response.buckets, response.bucketSize);
- const xMax = d3.max(buckets, (d) => d.x);
- const timeFormatter = getDurationFormatter(xMax);
-
- wrapper = mountWithTheme(
- timeFormatter(time).formatted}
- formatYShort={(t) => `${asInteger(t)} occ.`}
- formatYLong={(t) => `${asInteger(t)} occurrences`}
- tooltipHeader={(bucket) => {
- const xFormatted = timeFormatter(bucket.x);
- const x0Formatted = timeFormatter(bucket.x0);
- return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`;
- }}
- width={800}
- />
- );
- });
-
- describe('Initially', () => {
- it('should have default markup', () => {
- expect(toJson(wrapper)).toMatchSnapshot();
- });
-
- it('should not show tooltip', () => {
- expect(wrapper.find('Tooltip').length).toBe(0);
- });
- });
-
- describe('when hovering over an empty bucket', () => {
- beforeEach(() => {
- wrapper.find('.rv-voronoi__cell').at(2).simulate('mouseOver');
- });
-
- it('should not display tooltip', () => {
- expect(wrapper.find('Tooltip').length).toBe(0);
- });
- });
-
- describe('when hovering over a non-empty bucket', () => {
- beforeEach(() => {
- wrapper.find('.rv-voronoi__cell').at(7).simulate('mouseOver');
- });
-
- it('should display tooltip', () => {
- const tooltips = wrapper.find('Tooltip');
-
- expect(tooltips.length).toBe(1);
- expect(tooltips.prop('header')).toBe('811 - 927 ms');
- expect(tooltips.prop('tooltipPoints')).toEqual([
- { value: '49 occurrences' },
- ]);
- expect(tooltips.prop('x')).toEqual(869010);
- expect(tooltips.prop('y')).toEqual(27.5);
- });
-
- it('should have correct markup for tooltip', () => {
- const tooltips = wrapper.find('Tooltip');
- expect(toJson(tooltips)).toMatchSnapshot();
- });
- });
-
- describe('when clicking on a non-empty bucket', () => {
- beforeEach(() => {
- wrapper.find('.rv-voronoi__cell').at(7).simulate('click');
- });
-
- it('should call onClick with bucket', () => {
- expect(onClick).toHaveBeenCalledWith({
- style: { cursor: 'pointer' },
- xCenter: 869010,
- x0: 811076,
- x: 926944,
- y: 49,
- });
- });
- });
-});
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap
deleted file mode 100644
index a31b9735628ab..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap
+++ /dev/null
@@ -1,1504 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Histogram Initially should have default markup 1`] = `
-.c0 {
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0 ms
-
-
-
-
-
- 500 ms
-
-
-
-
-
- 1,000 ms
-
-
-
-
-
- 1,500 ms
-
-
-
-
-
- 2,000 ms
-
-
-
-
-
- 2,500 ms
-
-
-
-
-
- 3,000 ms
-
-
-
-
-
-
-
-
-
- 0 occ.
-
-
-
-
-
- 28 occ.
-
-
-
-
-
- 55 occ.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Histogram when hovering over a non-empty bucket should have correct markup for tooltip 1`] = `
-.c0 {
- margin: 0 16px;
- -webkit-transform: translateY(-50%);
- -ms-transform: translateY(-50%);
- transform: translateY(-50%);
- border: 1px solid #d3dae6;
- background: #ffffff;
- border-radius: 4px;
- font-size: 14px;
- color: #000000;
-}
-
-.c1 {
- background: #f5f7fa;
- border-bottom: 1px solid #d3dae6;
- border-radius: 4px 4px 0 0;
- padding: 8px;
- color: #98a2b3;
-}
-
-.c2 {
- margin: 8px;
- margin-right: 16px;
- font-size: 12px;
-}
-
-.c4 {
- color: #98a2b3;
- margin: 8px;
- font-size: 12px;
-}
-
-.c3 {
- color: #69707d;
- font-size: 14px;
-}
-
-
-
-
-
-
- 811 - 927 ms
-
-
-
-
-
-
- 49 occurrences
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json
deleted file mode 100644
index 302e105dfa997..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json
+++ /dev/null
@@ -1,106 +0,0 @@
-{
- "buckets": [
- { "key": 0, "count": 0 },
- { "key": 115868, "count": 0 },
- { "key": 231736, "count": 0 },
- { "key": 347604, "count": 0 },
- { "key": 463472, "count": 0 },
- {
- "key": 579340,
- "count": 8,
- "samples": [
- {
- "transactionId": "99437ee4-08d4-41f5-9b2b-93cc32ec3dfb"
- }
- ]
- },
- {
- "key": 695208,
- "count": 23,
- "samples": [
- {
- "transactionId": "d327611b-e999-4942-a94f-c60208940180"
- }
- ]
- },
- {
- "key": 811076,
- "count": 49,
- "samples": [
- {
- "transactionId": "99c50a5b-44b4-4289-a3d1-a2815d128192"
- }
- ]
- },
- {
- "key": 926944,
- "count": 51,
- "transactionId": "9706a1ec-23f5-4ce8-97e8-69ce35fb0a9a"
- },
- {
- "key": 1042812,
- "count": 46,
- "transactionId": "f8d360c3-dd5e-47b6-b082-9e0bf821d3b2"
- },
- {
- "key": 1158680,
- "count": 13,
- "samples": [
- {
- "transactionId": "8486d3e2-7f15-48df-aa37-6ee9955adbd2"
- }
- ]
- },
- {
- "key": 1274548,
- "count": 7,
- "transactionId": "54b4b5a7-f065-4cab-9016-534e58f4fc0a"
- },
- {
- "key": 1390416,
- "count": 4,
- "transactionId": "8cfac2a3-38e7-4d3a-9792-d008b4bcb867"
- },
- {
- "key": 1506284,
- "count": 3,
- "transactionId": "ce3f3bd3-a37c-419e-bb9c-5db956ded149"
- },
- { "key": 1622152, "count": 0 },
- {
- "key": 1738020,
- "count": 4,
- "transactionId": "2300174b-85d8-40ba-a6cb-eeba2a49debf"
- },
- { "key": 1853888, "count": 0 },
- { "key": 1969756, "count": 0 },
- {
- "key": 2085624,
- "count": 1,
- "transactionId": "774955a4-2ba3-4461-81a6-65759db4805d"
- },
- { "key": 2201492, "count": 0 },
- { "key": 2317360, "count": 0 },
- { "key": 2433228, "count": 0 },
- { "key": 2549096, "count": 0 },
- { "key": 2664964, "count": 0 },
- {
- "key": 2780832,
- "count": 1,
- "transactionId": "035d1b9d-af71-46cf-8910-57bd4faf412d"
- },
- {
- "key": 2896700,
- "count": 1,
- "transactionId": "4a845b32-9de4-4796-8ef4-d7bbdedc9099"
- },
- { "key": 3012568, "count": 0 },
- {
- "key": 3128436,
- "count": 1,
- "transactionId": "68620ffb-7a1b-4f8e-b9bb-009fa5b092be"
- }
- ],
- "bucketSize": 115868,
- "defaultBucketIndex": 12
-}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js
deleted file mode 100644
index 3b2109d68c613..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js
+++ /dev/null
@@ -1,319 +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 React, { PureComponent } from 'react';
-import d3 from 'd3';
-import { isEmpty } from 'lodash';
-import PropTypes from 'prop-types';
-import { scaleLinear } from 'd3-scale';
-import styled from 'styled-components';
-import SingleRect from './SingleRect';
-import {
- XYPlot,
- XAxis,
- YAxis,
- HorizontalGridLines,
- VerticalRectSeries,
- Voronoi,
- makeWidthFlexible,
- VerticalGridLines,
-} from 'react-vis';
-import { unit } from '../../../../style/variables';
-import Tooltip from '../Tooltip';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
-import { tint } from 'polished';
-import { getTimeTicksTZ, getDomainTZ } from '../helper/timezone';
-import Legends from '../CustomPlot/Legends';
-import StatusText from '../CustomPlot/StatusText';
-import { i18n } from '@kbn/i18n';
-import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
-
-const XY_HEIGHT = unit * 10;
-const XY_MARGIN = {
- top: unit,
- left: unit * 5,
- right: unit,
- bottom: unit * 2,
-};
-
-const X_TICK_TOTAL = 8;
-
-// position absolutely to make sure that window resizing/zooming works
-const ChartsWrapper = styled.div`
- user-select: none;
- position: absolute;
- top: 0;
- left: 0;
-`;
-
-export class HistogramInner extends PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- hoveredBucket: {},
- };
- }
-
- onClick = (bucket) => {
- if (this.props.onClick) {
- this.props.onClick(bucket);
- }
- };
-
- onHover = (bucket) => {
- this.setState({ hoveredBucket: bucket });
- };
-
- onBlur = () => {
- this.setState({ hoveredBucket: {} });
- };
-
- getChartData(items, selectedItem) {
- const yMax = d3.max(items, (d) => d.y);
- const MINIMUM_BUCKET_SIZE = yMax * 0.02;
-
- return items.map((item) => {
- const padding = (item.x - item.x0) / 20;
- return {
- ...item,
- color:
- item === selectedItem
- ? theme.euiColorVis1
- : tint(0.5, theme.euiColorVis1),
- x0: item.x0 + padding,
- x: item.x - padding,
- y: item.y > 0 ? Math.max(item.y, MINIMUM_BUCKET_SIZE) : 0,
- };
- });
- }
-
- render() {
- const {
- backgroundHover,
- bucketIndex,
- buckets,
- bucketSize,
- formatX,
- formatYShort,
- formatYLong,
- tooltipFooter,
- tooltipHeader,
- verticalLineHover,
- width: XY_WIDTH,
- height,
- legends,
- } = this.props;
- const { hoveredBucket } = this.state;
- if (isEmpty(buckets) || XY_WIDTH === 0) {
- return null;
- }
-
- const isTimeSeries =
- this.props.xType === 'time' || this.props.xType === 'time-utc';
-
- const xMin = d3.min(buckets, (d) => d.x0);
- const xMax = d3.max(buckets, (d) => d.x);
- const yMin = 0;
- const yMax = d3.max(buckets, (d) => d.y);
- const selectedBucket = buckets[bucketIndex];
- const chartData = this.getChartData(buckets, selectedBucket);
-
- const x = scaleLinear()
- .domain([xMin, xMax])
- .range([XY_MARGIN.left, XY_WIDTH - XY_MARGIN.right]);
-
- const y = scaleLinear().domain([yMin, yMax]).range([XY_HEIGHT, 0]).nice();
-
- const [xMinZone, xMaxZone] = getDomainTZ(xMin, xMax);
- const xTickValues = isTimeSeries
- ? getTimeTicksTZ({
- domain: [xMinZone, xMaxZone],
- totalTicks: X_TICK_TOTAL,
- width: XY_WIDTH,
- })
- : undefined;
-
- const xDomain = x.domain();
- const yDomain = y.domain();
- const yTickValues = [0, yDomain[1] / 2, yDomain[1]];
- const shouldShowTooltip =
- hoveredBucket.x > 0 && (hoveredBucket.y > 0 || isTimeSeries);
-
- const showVerticalLineHover = verticalLineHover(hoveredBucket);
- const showBackgroundHover = backgroundHover(hoveredBucket);
-
- const hasValidCoordinates = buckets.some((bucket) =>
- isValidCoordinateValue(bucket.y)
- );
- const noHits = this.props.noHits || !hasValidCoordinates;
-
- const xyPlotProps = {
- dontCheckIfEmpty: true,
- xType: this.props.xType,
- width: XY_WIDTH,
- height: XY_HEIGHT,
- margin: XY_MARGIN,
- xDomain: xDomain,
- yDomain: yDomain,
- };
-
- const xAxisProps = {
- style: { strokeWidth: '1px' },
- marginRight: 10,
- tickSize: 0,
- tickTotal: X_TICK_TOTAL,
- tickFormat: formatX,
- tickValues: xTickValues,
- };
-
- const emptyStateChart = (
-
-
-
-
- );
-
- return (
-
-
- {noHits ? (
- <>{emptyStateChart}>
- ) : (
- <>
-
-
-
-
-
- {showBackgroundHover && (
-
- )}
-
- {shouldShowTooltip && (
-
- )}
-
- {selectedBucket && (
-
- )}
-
-
-
- {showVerticalLineHover && hoveredBucket?.x && (
-
- )}
-
- {
- return {
- ...bucket,
- xCenter: (bucket.x0 + bucket.x) / 2,
- };
- })}
- onClick={this.onClick}
- onHover={this.onHover}
- onBlur={this.onBlur}
- x={(d) => x(d.xCenter)}
- y={() => 1}
- />
-
-
- {legends && (
- {}}
- truncateLegends={false}
- noHits={noHits}
- />
- )}
- >
- )}
-
-
- );
- }
-}
-
-HistogramInner.propTypes = {
- backgroundHover: PropTypes.func,
- bucketIndex: PropTypes.number,
- buckets: PropTypes.array.isRequired,
- bucketSize: PropTypes.number.isRequired,
- formatX: PropTypes.func,
- formatYLong: PropTypes.func,
- formatYShort: PropTypes.func,
- onClick: PropTypes.func,
- tooltipFooter: PropTypes.func,
- tooltipHeader: PropTypes.func,
- verticalLineHover: PropTypes.func,
- width: PropTypes.number.isRequired,
- height: PropTypes.number,
- xType: PropTypes.string,
- legends: PropTypes.array,
- noHits: PropTypes.bool,
-};
-
-HistogramInner.defaultProps = {
- backgroundHover: () => null,
- formatYLong: (value) => value,
- formatYShort: (value) => value,
- tooltipFooter: () => null,
- tooltipHeader: () => null,
- verticalLineHover: () => null,
- xType: 'linear',
- noHits: false,
- height: XY_HEIGHT,
-};
-
-export default makeWidthFlexible(HistogramInner);
diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx
deleted file mode 100644
index 2e4b51af00d6b..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx
+++ /dev/null
@@ -1,70 +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 React, { useCallback } from 'react';
-import { Coordinate, TimeSeries } from '../../../../../../typings/timeseries';
-import { useLegacyChartsSync as useChartsSync } from '../../../../../hooks/use_charts_sync';
-// @ts-expect-error
-import CustomPlot from '../../CustomPlot';
-
-interface Props {
- series: TimeSeries[];
- truncateLegends?: boolean;
- tickFormatY: (y: number) => React.ReactNode;
- formatTooltipValue: (c: Coordinate) => React.ReactNode;
- yMax?: string | number;
- height?: number;
- stacked?: boolean;
- onHover?: () => void;
- visibleLegendCount?: number;
- onToggleLegend?: (disabledSeriesState: boolean[]) => void;
-}
-
-function TransactionLineChart(props: Props) {
- const {
- series,
- tickFormatY,
- formatTooltipValue,
- yMax = 'max',
- height,
- truncateLegends,
- stacked = false,
- onHover,
- visibleLegendCount,
- onToggleLegend,
- } = props;
-
- const syncedChartsProps = useChartsSync();
-
- // combine callback for syncedChartsProps.onHover and props.onHover
- const combinedOnHover = useCallback(
- (hoverX: number) => {
- if (onHover) {
- onHover();
- }
- return syncedChartsProps.onHover(hoverX);
- },
- [syncedChartsProps, onHover]
- );
-
- return (
-
- );
-}
-
-export { TransactionLineChart };
diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx
index b3c0c3b6de857..2a5948d0ebf0b 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx
@@ -20,104 +20,107 @@ import {
TRANSACTION_REQUEST,
TRANSACTION_ROUTE_CHANGE,
} from '../../../../../common/transaction_types';
+import { asDecimal, tpmUnit } from '../../../../../common/utils/formatters';
import { Coordinate } from '../../../../../typings/timeseries';
+import { ChartsSyncContextProvider } from '../../../../context/charts_sync_context';
import { LicenseContext } from '../../../../context/LicenseContext';
import { IUrlParams } from '../../../../context/UrlParamsContext/types';
+import { FETCH_STATUS } from '../../../../hooks/useFetcher';
import { ITransactionChartData } from '../../../../selectors/chartSelectors';
-import { asDecimal, tpmUnit } from '../../../../../common/utils/formatters';
import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue';
-import { ErroneousTransactionsRateChart } from '../erroneous_transactions_rate_chart/legacy';
import { TransactionBreakdown } from '../../TransactionBreakdown';
-import {
- getResponseTimeTickFormatter,
- getResponseTimeTooltipFormatter,
-} from './helper';
+import { LineChart } from '../line_chart';
+import { TransactionErrorRateChart } from '../transaction_error_rate_chart/';
+import { getResponseTimeTickFormatter } from './helper';
import { MLHeader } from './ml_header';
-import { TransactionLineChart } from './TransactionLineChart';
import { useFormatter } from './use_formatter';
interface TransactionChartProps {
charts: ITransactionChartData;
urlParams: IUrlParams;
+ fetchStatus: FETCH_STATUS;
}
export function TransactionCharts({
charts,
urlParams,
+ fetchStatus,
}: TransactionChartProps) {
const getTPMFormatter = (t: number) => {
- const unit = tpmUnit(urlParams.transactionType);
- return `${asDecimal(t)} ${unit}`;
+ return `${asDecimal(t)} ${tpmUnit(urlParams.transactionType)}`;
};
- const getTPMTooltipFormatter = (p: Coordinate) => {
- return isValidCoordinateValue(p.y)
- ? getTPMFormatter(p.y)
- : NOT_AVAILABLE_LABEL;
+ const getTPMTooltipFormatter = (y: Coordinate['y']) => {
+ return isValidCoordinateValue(y) ? getTPMFormatter(y) : NOT_AVAILABLE_LABEL;
};
const { transactionType } = urlParams;
const { responseTimeSeries, tpmSeries } = charts;
- const { formatter, setDisabledSeriesState } = useFormatter(
- responseTimeSeries
- );
+ const { formatter, toggleSerie } = useFormatter(responseTimeSeries);
return (
<>
-
-
-
-
-
-
- {responseTimeLabel(transactionType)}
-
-
-
- {(license) => (
-
- )}
-
-
-
-
-
+
+
+
+
+
+
+
+ {responseTimeLabel(transactionType)}
+
+
+
+ {(license) => (
+
+ )}
+
+
+ {
+ if (serie) {
+ toggleSerie(serie);
+ }
+ }}
+ />
+
+
-
-
-
- {tpmLabel(transactionType)}
-
-
-
-
-
+
+
+
+ {tpmLabel(transactionType)}
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
>
);
}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx
index fc873cbda7bf2..958a5db6b66c9 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx
@@ -3,38 +3,17 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import { SeriesIdentifier } from '@elastic/charts';
+import { renderHook } from '@testing-library/react-hooks';
+import { act } from 'react-test-renderer';
+import { toMicroseconds } from '../../../../../common/utils/formatters';
import { TimeSeries } from '../../../../../typings/timeseries';
import { useFormatter } from './use_formatter';
-import { render, fireEvent, act } from '@testing-library/react';
-import { toMicroseconds } from '../../../../../common/utils/formatters';
-
-function MockComponent({
- timeSeries,
- disabledSeries,
- value,
-}: {
- timeSeries: TimeSeries[];
- disabledSeries: boolean[];
- value: number;
-}) {
- const { formatter, setDisabledSeriesState } = useFormatter(timeSeries);
-
- const onDisableSeries = () => {
- setDisabledSeriesState(disabledSeries);
- };
-
- return (
-
- disable series
- {formatter(value).formatted}
-
- );
-}
describe('useFormatter', () => {
const timeSeries = ([
{
+ title: 'avg',
data: [
{ x: 1, y: toMicroseconds(11, 'minutes') },
{ x: 2, y: toMicroseconds(1, 'minutes') },
@@ -42,6 +21,7 @@ describe('useFormatter', () => {
],
},
{
+ title: '95th percentile',
data: [
{ x: 1, y: toMicroseconds(120, 'seconds') },
{ x: 2, y: toMicroseconds(1, 'minutes') },
@@ -49,6 +29,7 @@ describe('useFormatter', () => {
],
},
{
+ title: '99th percentile',
data: [
{ x: 1, y: toMicroseconds(60, 'seconds') },
{ x: 2, y: toMicroseconds(5, 'minutes') },
@@ -56,54 +37,47 @@ describe('useFormatter', () => {
],
},
] as unknown) as TimeSeries[];
+
it('returns new formatter when disabled series state changes', () => {
- const { getByText } = render(
-
- );
- expect(getByText('2.0 min')).toBeInTheDocument();
+ const { result } = renderHook(() => useFormatter(timeSeries));
+ expect(
+ result.current.formatter(toMicroseconds(120, 'seconds')).formatted
+ ).toEqual('2.0 min');
+
act(() => {
- fireEvent.click(getByText('disable series'));
+ result.current.toggleSerie({
+ specId: 'avg',
+ } as SeriesIdentifier);
});
- expect(getByText('120 s')).toBeInTheDocument();
+
+ expect(
+ result.current.formatter(toMicroseconds(120, 'seconds')).formatted
+ ).toEqual('120 s');
});
+
it('falls back to the first formatter when disabled series is empty', () => {
- const { getByText } = render(
-
- );
- expect(getByText('2.0 min')).toBeInTheDocument();
+ const { result } = renderHook(() => useFormatter(timeSeries));
+ expect(
+ result.current.formatter(toMicroseconds(120, 'seconds')).formatted
+ ).toEqual('2.0 min');
+
act(() => {
- fireEvent.click(getByText('disable series'));
+ result.current.toggleSerie({
+ specId: 'avg',
+ } as SeriesIdentifier);
});
- expect(getByText('2.0 min')).toBeInTheDocument();
- // const { formatter, setDisabledSeriesState } = useFormatter(timeSeries);
- // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min');
- // setDisabledSeriesState([true, true, false]);
- // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min');
- });
- it('falls back to the first formatter when disabled series is all true', () => {
- const { getByText } = render(
-
- );
- expect(getByText('2.0 min')).toBeInTheDocument();
+
+ expect(
+ result.current.formatter(toMicroseconds(120, 'seconds')).formatted
+ ).toEqual('120 s');
+
act(() => {
- fireEvent.click(getByText('disable series'));
+ result.current.toggleSerie({
+ specId: 'avg',
+ } as SeriesIdentifier);
});
- expect(getByText('2.0 min')).toBeInTheDocument();
- // const { formatter, setDisabledSeriesState } = useFormatter(timeSeries);
- // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min');
- // setDisabledSeriesState([true, true, false]);
- // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min');
+ expect(
+ result.current.formatter(toMicroseconds(120, 'seconds')).formatted
+ ).toEqual('2.0 min');
});
});
diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts
index d4694bc3caf1d..1475ec2934e95 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts
+++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts
@@ -4,8 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { useState, Dispatch, SetStateAction } from 'react';
-import { isEmpty } from 'lodash';
+import { SeriesIdentifier } from '@elastic/charts';
+import { omit } from 'lodash';
+import { useState } from 'react';
import {
getDurationFormatter,
TimeFormatter,
@@ -14,17 +15,36 @@ import { TimeSeries } from '../../../../../typings/timeseries';
import { getMaxY } from './helper';
export const useFormatter = (
- series: TimeSeries[]
+ series?: TimeSeries[]
): {
formatter: TimeFormatter;
- setDisabledSeriesState: Dispatch>;
+ toggleSerie: (disabledSerie: SeriesIdentifier) => void;
} => {
- const [disabledSeriesState, setDisabledSeriesState] = useState([]);
- const visibleSeries = series.filter(
- (serie, index) => disabledSeriesState[index] !== true
+ const [disabledSeries, setDisabledSeries] = useState<
+ Record
+ >({});
+
+ const visibleSeries = series?.filter(
+ (serie) => disabledSeries[serie.title] === undefined
);
- const maxY = getMaxY(isEmpty(visibleSeries) ? series : visibleSeries);
+
+ const maxY = getMaxY(visibleSeries || series || []);
const formatter = getDurationFormatter(maxY);
- return { formatter, setDisabledSeriesState };
+ const toggleSerie = ({ specId }: SeriesIdentifier) => {
+ if (disabledSeries[specId] !== undefined) {
+ setDisabledSeries((prevState) => {
+ return omit(prevState, specId);
+ });
+ } else {
+ setDisabledSeries((prevState) => {
+ return { ...prevState, [specId]: 0 };
+ });
+ }
+ };
+
+ return {
+ formatter,
+ toggleSerie,
+ };
};
diff --git a/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx
new file mode 100644
index 0000000000000..683c66b2a96fe
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+ AnnotationDomainTypes,
+ LineAnnotation,
+ Position,
+} from '@elastic/charts';
+import { EuiIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { asAbsoluteDateTime } from '../../../../../common/utils/formatters';
+import { useTheme } from '../../../../hooks/useTheme';
+import { useAnnotations } from '../../../../hooks/use_annotations';
+
+export function Annotations() {
+ const { annotations } = useAnnotations();
+ const theme = useTheme();
+
+ if (!annotations.length) {
+ return null;
+ }
+
+ const color = theme.eui.euiColorSecondary;
+
+ return (
+ ({
+ dataValue: annotation['@timestamp'],
+ header: asAbsoluteDateTime(annotation['@timestamp']),
+ details: `${i18n.translate('xpack.apm.chart.annotation.version', {
+ defaultMessage: 'Version',
+ })} ${annotation.text}`,
+ }))}
+ style={{ line: { strokeWidth: 1, stroke: color, opacity: 1 } }}
+ marker={ }
+ markerPosition={Position.Top}
+ />
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx
index 409cb69575ca9..c0e8f869ce647 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx
@@ -5,30 +5,97 @@
*/
import { render } from '@testing-library/react';
import React from 'react';
+import { FETCH_STATUS } from '../../../hooks/useFetcher';
import { ChartContainer } from './chart_container';
describe('ChartContainer', () => {
- describe('when isLoading is true', () => {
- it('shows loading the indicator', () => {
- const component = render(
-
+ describe('loading indicator', () => {
+ it('shows loading when status equals to Loading or Pending and has no data', () => {
+ [FETCH_STATUS.PENDING, FETCH_STATUS.LOADING].map((status) => {
+ const { queryAllByTestId } = render(
+
+ My amazing component
+
+ );
+
+ expect(queryAllByTestId('loading')[0]).toBeInTheDocument();
+ });
+ });
+ it('does not show loading when status equals to Loading or Pending and has data', () => {
+ [FETCH_STATUS.PENDING, FETCH_STATUS.LOADING].map((status) => {
+ const { queryAllByText } = render(
+
+ My amazing component
+
+ );
+ expect(queryAllByText('My amazing component')[0]).toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('failure indicator', () => {
+ it('shows failure message when status equals to Failure and has data', () => {
+ const { getByText } = render(
+
My amazing component
);
-
- expect(component.getByTestId('loading')).toBeInTheDocument();
+ expect(
+ getByText(
+ 'An error happened when trying to fetch data. Please try again'
+ )
+ ).toBeInTheDocument();
+ });
+ it('shows failure message when status equals to Failure and has no data', () => {
+ const { getByText } = render(
+
+ My amazing component
+
+ );
+ expect(
+ getByText(
+ 'An error happened when trying to fetch data. Please try again'
+ )
+ ).toBeInTheDocument();
});
});
- describe('when isLoading is false', () => {
- it('does not show the loading indicator', () => {
- const component = render(
-
+ describe('render component', () => {
+ it('shows children component when status Success and has data', () => {
+ const { getByText } = render(
+
My amazing component
);
-
- expect(component.queryByTestId('loading')).not.toBeInTheDocument();
+ expect(getByText('My amazing component')).toBeInTheDocument();
+ });
+ it('shows children component when status Success and has no data', () => {
+ const { getByText } = render(
+
+ My amazing component
+
+ );
+ expect(getByText('My amazing component')).toBeInTheDocument();
});
});
});
diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx
index a6f579308597f..b4486f1e9b94a 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx
@@ -3,27 +3,56 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiLoadingChart } from '@elastic/eui';
+
+import { EuiLoadingChart, EuiText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import React from 'react';
+import { FETCH_STATUS } from '../../../hooks/useFetcher';
interface Props {
- isLoading: boolean;
+ hasData: boolean;
+ status: FETCH_STATUS;
height: number;
children: React.ReactNode;
}
-export function ChartContainer({ isLoading, children, height }: Props) {
+export function ChartContainer({ children, height, status, hasData }: Props) {
+ if (
+ !hasData &&
+ (status === FETCH_STATUS.LOADING || status === FETCH_STATUS.PENDING)
+ ) {
+ return ;
+ }
+
+ if (status === FETCH_STATUS.FAILURE) {
+ return ;
+ }
+
+ return {children}
;
+}
+
+function LoadingChartPlaceholder({ height }: { height: number }) {
return (
- {isLoading && }
- {children}
+
);
}
+
+function FailedChartPlaceholder({ height }: { height: number }) {
+ return (
+
+ {i18n.translate('xpack.apm.chart.error', {
+ defaultMessage:
+ 'An error happened when trying to fetch data. Please try again',
+ })}
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx b/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx
deleted file mode 100644
index 29102f606414f..0000000000000
--- a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx
+++ /dev/null
@@ -1,112 +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 { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
-import { i18n } from '@kbn/i18n';
-import { max } from 'lodash';
-import React, { useCallback } from 'react';
-import { useParams } from 'react-router-dom';
-import { asPercent } from '../../../../../common/utils/formatters';
-import { useLegacyChartsSync as useChartsSync } from '../../../../hooks/use_charts_sync';
-import { useFetcher } from '../../../../hooks/useFetcher';
-import { useUrlParams } from '../../../../hooks/useUrlParams';
-import { callApmApi } from '../../../../services/rest/createCallApmApi';
-// @ts-expect-error
-import CustomPlot from '../CustomPlot';
-
-const tickFormatY = (y?: number | null) => {
- return asPercent(y || 0, 1);
-};
-
-/**
- * "Legacy" version of this chart using react-vis charts. See index.tsx for the
- * Elastic Charts version.
- *
- * This will be removed with #70290.
- */
-export function ErroneousTransactionsRateChart() {
- const { serviceName } = useParams<{ serviceName?: string }>();
- const { urlParams, uiFilters } = useUrlParams();
- const syncedChartsProps = useChartsSync();
-
- const { start, end, transactionType, transactionName } = urlParams;
-
- const { data } = useFetcher(() => {
- if (serviceName && start && end) {
- return callApmApi({
- pathname:
- '/api/apm/services/{serviceName}/transaction_groups/error_rate',
- params: {
- path: {
- serviceName,
- },
- query: {
- start,
- end,
- transactionType,
- transactionName,
- uiFilters: JSON.stringify(uiFilters),
- },
- },
- });
- }
- }, [serviceName, start, end, uiFilters, transactionType, transactionName]);
-
- const combinedOnHover = useCallback(
- (hoverX: number) => {
- return syncedChartsProps.onHover(hoverX);
- },
- [syncedChartsProps]
- );
-
- const errorRates = data?.transactionErrorRate || [];
- const maxRate = max(errorRates.map((errorRate) => errorRate.y));
-
- return (
-
-
-
- {i18n.translate('xpack.apm.errorRateChart.title', {
- defaultMessage: 'Transaction error rate',
- })}
-
-
-
-
- Number.isFinite(y) ? tickFormatY(y) : 'N/A'
- }
- />
-
- );
-}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx
index 3f2a08ecb7641..507acc49d89db 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx
@@ -20,15 +20,17 @@ import moment from 'moment';
import React, { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { TimeSeries } from '../../../../../typings/timeseries';
+import { FETCH_STATUS } from '../../../../hooks/useFetcher';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { useChartsSync } from '../../../../hooks/use_charts_sync';
import { unit } from '../../../../style/variables';
+import { Annotations } from '../annotations';
import { ChartContainer } from '../chart_container';
import { onBrushEnd } from '../helper/helper';
interface Props {
id: string;
- isLoading: boolean;
+ fetchStatus: FETCH_STATUS;
onToggleLegend?: LegendItemListener;
timeseries: TimeSeries[];
/**
@@ -38,18 +40,20 @@ interface Props {
/**
* Formatter for legend and tooltip values
*/
- yTickFormat: (y: number) => string;
+ yTickFormat?: (y: number) => string;
+ showAnnotations?: boolean;
}
const XY_HEIGHT = unit * 16;
export function LineChart({
id,
- isLoading,
+ fetchStatus,
onToggleLegend,
timeseries,
yLabelFormat,
yTickFormat,
+ showAnnotations = true,
}: Props) {
const history = useHistory();
const chartRef = React.createRef();
@@ -84,7 +88,7 @@ export function LineChart({
);
return (
-
+
onBrushEnd({ x, history })}
@@ -115,11 +119,13 @@ export function LineChart({
id="y-axis"
ticks={3}
position={Position.Left}
- tickFormat={yTickFormat}
+ tickFormat={yTickFormat ? yTickFormat : yLabelFormat}
labelFormat={yLabelFormat}
showGridLines
/>
+ {showAnnotations && }
+
{timeseries.map((serie) => {
return (
();
const { urlParams, uiFilters } = useUrlParams();
@@ -56,25 +61,32 @@ export function ErroneousTransactionsRateChart() {
const errorRates = data?.transactionErrorRate || [];
return (
-
+
+
+
+ {i18n.translate('xpack.apm.errorRate', {
+ defaultMessage: 'Error rate',
+ })}
+
+
+
+
);
}
diff --git a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts
index 08d2300c3254a..0705383ecb0ca 100644
--- a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts
+++ b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts
@@ -15,7 +15,7 @@ export function useTransactionBreakdown() {
uiFilters,
} = useUrlParams();
- const { data = { timeseries: [] }, error, status } = useFetcher(
+ const { data = { timeseries: undefined }, error, status } = useFetcher(
(callApmApi) => {
if (serviceName && start && end && transactionType) {
return callApmApi({
diff --git a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts b/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts
index a5096a314388c..8c76225d03486 100644
--- a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts
+++ b/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts
@@ -10,7 +10,7 @@ import { IUrlParams } from '../context/UrlParamsContext/types';
import { useFetcher } from './useFetcher';
import { useUiFilters } from '../context/UrlParamsContext';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { TransactionDistributionAPIResponse } from '../../server/lib/transactions/distribution';
+import type { TransactionDistributionAPIResponse } from '../../server/lib/transactions/distribution';
import { toQuery, fromQuery } from '../components/shared/Links/url_helpers';
import { maybe } from '../../common/utils/maybe';
diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/hooks/useTransactionList.ts
index 9c3a18b9c0d0d..b2c2cc30f78ec 100644
--- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts
+++ b/x-pack/plugins/apm/public/hooks/useTransactionList.ts
@@ -14,8 +14,8 @@ type TransactionsAPIResponse = APIReturnType<
'/api/apm/services/{serviceName}/transaction_groups'
>;
-const DEFAULT_RESPONSE: TransactionsAPIResponse = {
- items: [],
+const DEFAULT_RESPONSE: Partial = {
+ items: undefined,
isAggregationAccurate: true,
bucketSize: 0,
};
diff --git a/x-pack/plugins/apm/public/hooks/use_annotations.ts b/x-pack/plugins/apm/public/hooks/use_annotations.ts
new file mode 100644
index 0000000000000..2b1c2bec52b3d
--- /dev/null
+++ b/x-pack/plugins/apm/public/hooks/use_annotations.ts
@@ -0,0 +1,38 @@
+/*
+ * 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 { useParams } from 'react-router-dom';
+import { callApmApi } from '../services/rest/createCallApmApi';
+import { useFetcher } from './useFetcher';
+import { useUrlParams } from './useUrlParams';
+
+const INITIAL_STATE = { annotations: [] };
+
+export function useAnnotations() {
+ const { serviceName } = useParams<{ serviceName?: string }>();
+ const { urlParams, uiFilters } = useUrlParams();
+ const { start, end } = urlParams;
+ const { environment } = uiFilters;
+
+ const { data = INITIAL_STATE } = useFetcher(() => {
+ if (start && end && serviceName) {
+ return callApmApi({
+ pathname: '/api/apm/services/{serviceName}/annotation/search',
+ params: {
+ path: {
+ serviceName,
+ },
+ query: {
+ start,
+ end,
+ environment,
+ },
+ },
+ });
+ }
+ }, [start, end, environment, serviceName]);
+
+ return data;
+}
diff --git a/x-pack/plugins/apm/public/selectors/chartSelectors.ts b/x-pack/plugins/apm/public/selectors/chartSelectors.ts
index 8c6093859f969..450f02f70c6a4 100644
--- a/x-pack/plugins/apm/public/selectors/chartSelectors.ts
+++ b/x-pack/plugins/apm/public/selectors/chartSelectors.ts
@@ -31,40 +31,37 @@ export interface ITpmBucket {
}
export interface ITransactionChartData {
- tpmSeries: ITpmBucket[];
- responseTimeSeries: TimeSeries[];
+ tpmSeries?: ITpmBucket[];
+ responseTimeSeries?: TimeSeries[];
mlJobId: string | undefined;
}
-const INITIAL_DATA = {
- apmTimeseries: {
- responseTimes: {
- avg: [],
- p95: [],
- p99: [],
- },
- tpmBuckets: [],
- overallAvgDuration: null,
- },
+const INITIAL_DATA: Partial = {
+ apmTimeseries: undefined,
anomalyTimeseries: undefined,
};
export function getTransactionCharts(
{ transactionType }: IUrlParams,
- { apmTimeseries, anomalyTimeseries }: TimeSeriesAPIResponse = INITIAL_DATA
+ charts = INITIAL_DATA
): ITransactionChartData {
- const tpmSeries = getTpmSeries(apmTimeseries, transactionType);
-
- const responseTimeSeries = getResponseTimeSeries({
- apmTimeseries,
- anomalyTimeseries,
- });
+ const { apmTimeseries, anomalyTimeseries } = charts;
- return {
- tpmSeries,
- responseTimeSeries,
+ const transactionCharts: ITransactionChartData = {
+ tpmSeries: undefined,
+ responseTimeSeries: undefined,
mlJobId: anomalyTimeseries?.jobId,
};
+
+ if (apmTimeseries) {
+ transactionCharts.tpmSeries = getTpmSeries(apmTimeseries, transactionType);
+
+ transactionCharts.responseTimeSeries = getResponseTimeSeries({
+ apmTimeseries,
+ anomalyTimeseries,
+ });
+ }
+ return transactionCharts;
}
export function getResponseTimeSeries({
diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts
index 090110b0454c0..29a0d1fdf4249 100644
--- a/x-pack/plugins/apm/server/index.ts
+++ b/x-pack/plugins/apm/server/index.ts
@@ -43,6 +43,7 @@ export const config = {
),
telemetryCollectionEnabled: schema.boolean({ defaultValue: true }),
metricsInterval: schema.number({ defaultValue: 30 }),
+ maxServiceEnvironments: schema.number({ defaultValue: 100 }),
}),
};
@@ -74,6 +75,7 @@ export function mergeConfigs(
'xpack.apm.serviceMapMaxTracesPerRequest':
apmConfig.serviceMapMaxTracesPerRequest,
'xpack.apm.ui.enabled': apmConfig.ui.enabled,
+ 'xpack.apm.maxServiceEnvironments': apmConfig.maxServiceEnvironments,
'xpack.apm.ui.maxTraceItems': apmConfig.ui.maxTraceItems,
'xpack.apm.ui.transactionGroupBucketSize':
apmConfig.ui.transactionGroupBucketSize,
diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts
index 7b63f2c354916..ecda5b0e8504b 100644
--- a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts
@@ -66,6 +66,7 @@ export function registerErrorCountAlertType({
config,
savedObjectsClient: services.savedObjectsClient,
});
+ const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
const searchParams = {
index: indices['apm_oss.errorIndices'],
@@ -100,6 +101,7 @@ export function registerErrorCountAlertType({
environments: {
terms: {
field: SERVICE_ENVIRONMENT,
+ size: maxServiceEnvironments,
},
},
},
diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts
index 1d8b664751ba2..d9e69c8f3b7d7 100644
--- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts
@@ -75,6 +75,7 @@ export function registerTransactionDurationAlertType({
config,
savedObjectsClient: services.savedObjectsClient,
});
+ const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
const searchParams = {
index: indices['apm_oss.transactionIndices'],
@@ -112,6 +113,7 @@ export function registerTransactionDurationAlertType({
environments: {
terms: {
field: SERVICE_ENVIRONMENT,
+ size: maxServiceEnvironments,
},
},
},
diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts
index 969f4ceaca93a..06b296db5a485 100644
--- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts
@@ -71,6 +71,7 @@ export function registerTransactionErrorRateAlertType({
config,
savedObjectsClient: services.savedObjectsClient,
});
+ const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
const searchParams = {
index: indices['apm_oss.transactionIndices'],
@@ -120,6 +121,7 @@ export function registerTransactionErrorRateAlertType({
environments: {
terms: {
field: SERVICE_ENVIRONMENT,
+ size: maxServiceEnvironments,
},
},
},
diff --git a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts
index 95ff357937d47..39b4f7a7fe81b 100644
--- a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts
+++ b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts
@@ -24,7 +24,8 @@ export async function getAllEnvironments({
searchAggregatedTransactions: boolean;
includeMissing?: boolean;
}) {
- const { apmEventClient } = setup;
+ const { apmEventClient, config } = setup;
+ const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
// omit filter for service.name if "All" option is selected
const serviceNameFilter = serviceName
@@ -55,7 +56,7 @@ export async function getAllEnvironments({
environments: {
terms: {
field: SERVICE_ENVIRONMENT,
- size: 100,
+ size: maxServiceEnvironments,
...(!serviceName ? { min_doc_count: 0 } : {}),
missing: includeMissing ? ENVIRONMENT_NOT_DEFINED.value : undefined,
},
diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts
index a42710947a792..b12dd73a20986 100644
--- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts
+++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts
@@ -73,6 +73,6 @@ export async function getBuckets({
return {
noHits: resp.hits.total.value === 0,
- buckets,
+ buckets: resp.hits.total.value > 0 ? buckets : [],
};
}
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
index 2bfd3c94ed34c..9020cb1b9953a 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
@@ -7,14 +7,16 @@
import { ValuesType } from 'utility-types';
import { APMBaseDoc } from '../../../../../typings/es_schemas/raw/apm_base_doc';
import { APMError } from '../../../../../typings/es_schemas/ui/apm_error';
-import { KibanaRequest } from '../../../../../../../../src/core/server';
+import {
+ KibanaRequest,
+ LegacyScopedClusterClient,
+} from '../../../../../../../../src/core/server';
import { ProcessorEvent } from '../../../../../common/processor_event';
import {
ESSearchRequest,
ESSearchResponse,
} from '../../../../../typings/elasticsearch';
import { ApmIndicesConfig } from '../../../settings/apm_indices/get_apm_indices';
-import { APMRequestHandlerContext } from '../../../../routes/typings';
import { addFilterToExcludeLegacyData } from './add_filter_to_exclude_legacy_data';
import { callClientWithDebug } from '../call_client_with_debug';
import { Transaction } from '../../../../../typings/es_schemas/ui/transaction';
@@ -51,20 +53,23 @@ type TypedSearchResponse<
export type APMEventClient = ReturnType;
export function createApmEventClient({
- context,
+ esClient,
+ debug,
request,
indices,
options: { includeFrozen } = { includeFrozen: false },
}: {
- context: APMRequestHandlerContext;
+ esClient: Pick<
+ LegacyScopedClusterClient,
+ 'callAsInternalUser' | 'callAsCurrentUser'
+ >;
+ debug: boolean;
request: KibanaRequest;
indices: ApmIndicesConfig;
options: {
includeFrozen: boolean;
};
}) {
- const client = context.core.elasticsearch.legacy.client;
-
return {
search(
params: TParams,
@@ -77,14 +82,14 @@ export function createApmEventClient({
: withProcessorEventFilter;
return callClientWithDebug({
- apiCaller: client.callAsCurrentUser,
+ apiCaller: esClient.callAsCurrentUser,
operationName: 'search',
params: {
...withPossibleLegacyDataFilter,
ignore_throttled: !includeFrozen,
},
request,
- debug: context.params.query._debug,
+ debug,
});
},
};
diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
index 5e75535c678b3..363c4128137e0 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
@@ -88,7 +88,8 @@ export async function setupRequest(
const coreSetupRequest = {
indices,
apmEventClient: createApmEventClient({
- context,
+ esClient: context.core.elasticsearch.legacy.client,
+ debug: context.params.query._debug,
request,
indices,
options: { includeFrozen },
diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
index 3a38f80c87b35..a6818f96c728e 100644
--- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
@@ -366,6 +366,7 @@ Array [
"environments": Object {
"terms": Object {
"field": "service.environment",
+ "size": 100,
},
},
},
diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts
index 7d190c5b66450..fac80cf22c310 100644
--- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts
@@ -337,7 +337,8 @@ export const getEnvironments = async ({
setup,
projection,
}: AggregationParams) => {
- const { apmEventClient } = setup;
+ const { apmEventClient, config } = setup;
+ const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
const response = await apmEventClient.search(
mergeProjection(projection, {
body: {
@@ -352,6 +353,7 @@ export const getEnvironments = async ({
environments: {
terms: {
field: SERVICE_ENVIRONMENT,
+ size: maxServiceEnvironments,
},
},
},
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap
index 8db97a4929eb0..18ef3f44331d9 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap
@@ -127,7 +127,7 @@ Object {
"terms": Object {
"field": "service.environment",
"missing": "ALL_OPTION_VALUE",
- "size": 50,
+ "size": 100,
},
},
},
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts
index 8327ac59a95b2..5e19f8f211cf7 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts
@@ -18,7 +18,8 @@ export async function getExistingEnvironmentsForService({
serviceName: string | undefined;
setup: Setup;
}) {
- const { internalClient, indices } = setup;
+ const { internalClient, indices, config } = setup;
+ const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
const bool = serviceName
? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] }
@@ -34,7 +35,7 @@ export async function getExistingEnvironmentsForService({
terms: {
field: SERVICE_ENVIRONMENT,
missing: ALL_OPTION_VALUE,
- size: 50,
+ size: maxServiceEnvironments,
},
},
},
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap
index d94b766aee6a8..3baaefe203ce7 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap
@@ -15,6 +15,7 @@ Object {
"terms": Object {
"field": "service.environment",
"missing": "ENVIRONMENT_NOT_DEFINED",
+ "size": 100,
},
},
},
@@ -58,6 +59,7 @@ Object {
"terms": Object {
"field": "service.environment",
"missing": "ENVIRONMENT_NOT_DEFINED",
+ "size": 100,
},
},
},
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts
index e72cc7e2483ad..b9f25e20f9f73 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts
+++ b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts
@@ -24,7 +24,7 @@ export async function getEnvironments({
serviceName?: string;
searchAggregatedTransactions: boolean;
}) {
- const { start, end, apmEventClient } = setup;
+ const { start, end, apmEventClient, config } = setup;
const filter: ESFilter[] = [{ range: rangeFilter(start, end) }];
@@ -34,6 +34,8 @@ export async function getEnvironments({
});
}
+ const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
+
const params = {
apm: {
events: [
@@ -56,6 +58,7 @@ export async function getEnvironments({
terms: {
field: SERVICE_ENVIRONMENT,
missing: ENVIRONMENT_NOT_DEFINED.value,
+ size: maxServiceEnvironments,
},
},
},
diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts
index d3341b6c1b163..44269b1775953 100644
--- a/x-pack/plugins/apm/server/plugin.ts
+++ b/x-pack/plugins/apm/server/plugin.ts
@@ -10,14 +10,17 @@ import { map, take } from 'rxjs/operators';
import {
CoreSetup,
CoreStart,
+ KibanaRequest,
Logger,
Plugin,
PluginInitializerContext,
+ RequestHandlerContext,
} from 'src/core/server';
import { APMConfig, APMXPackConfig, mergeConfigs } from '.';
import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server';
import { HomeServerPluginSetup } from '../../../../src/plugins/home/server';
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server';
+import { UI_SETTINGS } from '../../../../src/plugins/data/common';
import { ActionsPlugin } from '../../actions/server';
import { AlertingPlugin } from '../../alerts/server';
import { CloudSetup } from '../../cloud/server';
@@ -30,6 +33,7 @@ import { TaskManagerSetupContract } from '../../task_manager/server';
import { APM_FEATURE, registerFeaturesUsage } from './feature';
import { registerApmAlerts } from './lib/alerts/register_apm_alerts';
import { createApmTelemetry } from './lib/apm_telemetry';
+import { createApmEventClient } from './lib/helpers/create_es_client/create_apm_event_client';
import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_objects_client';
import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index';
import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices';
@@ -42,6 +46,11 @@ import { uiSettings } from './ui_settings';
export interface APMPluginSetup {
config$: Observable;
getApmIndices: () => ReturnType;
+ createApmEventClient: (params: {
+ debug?: boolean;
+ request: KibanaRequest;
+ context: RequestHandlerContext;
+ }) => Promise>;
}
export class APMPlugin implements Plugin {
@@ -141,13 +150,41 @@ export class APMPlugin implements Plugin {
},
});
+ const boundGetApmIndices = async () =>
+ getApmIndices({
+ savedObjectsClient: await getInternalSavedObjectsClient(core),
+ config: await mergedConfig$.pipe(take(1)).toPromise(),
+ });
+
return {
config$: mergedConfig$,
- getApmIndices: async () =>
- getApmIndices({
- savedObjectsClient: await getInternalSavedObjectsClient(core),
- config: await mergedConfig$.pipe(take(1)).toPromise(),
- }),
+ getApmIndices: boundGetApmIndices,
+ createApmEventClient: async ({
+ request,
+ context,
+ debug,
+ }: {
+ debug?: boolean;
+ request: KibanaRequest;
+ context: RequestHandlerContext;
+ }) => {
+ const [indices, includeFrozen] = await Promise.all([
+ boundGetApmIndices(),
+ context.core.uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN),
+ ]);
+
+ const esClient = context.core.elasticsearch.legacy.client;
+
+ return createApmEventClient({
+ debug: debug ?? false,
+ esClient,
+ request,
+ indices,
+ options: {
+ includeFrozen,
+ },
+ });
+ },
};
}
diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx
index 18b990b35b5a5..21b59dc516d06 100644
--- a/x-pack/plugins/apm/server/utils/test_helpers.tsx
+++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx
@@ -76,6 +76,9 @@ export async function inspectSearchParams(
case 'xpack.apm.metricsInterval':
return 30;
+
+ case 'xpack.apm.maxServiceEnvironments':
+ return 100;
}
},
}
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot
index 5ad49a207ed3e..286c55994f27e 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot
@@ -6,6 +6,7 @@ exports[`Storyshots renderers/DropdownFilter default 1`] = `
>
@@ -109,6 +112,7 @@ exports[`Storyshots renderers/DropdownFilter with choices and value 1`] = `
>
@@ -150,6 +154,7 @@ exports[`Storyshots renderers/DropdownFilter with new value 1`] = `
>
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/dropdown_filter.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/dropdown_filter.tsx
index ac08d3dd25d85..c6173bb829155 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/dropdown_filter.tsx
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/dropdown_filter.tsx
@@ -62,7 +62,12 @@ export const DropdownFilter: FunctionComponent = ({
/* eslint-disable jsx-a11y/no-onchange */
return (
-
+
{dropdownOptions}
diff --git a/x-pack/plugins/canvas/public/components/debug/__stories__/__snapshots__/debug.stories.storyshot b/x-pack/plugins/canvas/public/components/debug/__stories__/__snapshots__/debug.stories.storyshot
index 86d5dbc39197f..584cbdaa793a2 100644
--- a/x-pack/plugins/canvas/public/components/debug/__stories__/__snapshots__/debug.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/debug/__stories__/__snapshots__/debug.stories.storyshot
@@ -5,6 +5,7 @@ exports[`Storyshots components/Elements/Debug large payload 1`] = `
{
"datatable": {
@@ -1256,6 +1257,7 @@ exports[`Storyshots components/Elements/Debug small payload 1`] = `
{
"datatable": {
diff --git a/x-pack/plugins/canvas/public/components/debug/debug.tsx b/x-pack/plugins/canvas/public/components/debug/debug.tsx
index eb8189d479f4c..9bce152490f31 100644
--- a/x-pack/plugins/canvas/public/components/debug/debug.tsx
+++ b/x-pack/plugins/canvas/public/components/debug/debug.tsx
@@ -17,7 +17,9 @@ const LimitRows = (key: string, value: any) => {
export const Debug = ({ payload }: any) => (
- {JSON.stringify(payload, LimitRows, 2)}
+
+ {JSON.stringify(payload, LimitRows, 2)}
+
);
diff --git a/x-pack/plugins/canvas/public/components/element_content/element_content.js b/x-pack/plugins/canvas/public/components/element_content/element_content.js
index e2c1a61c348d1..2c1d9621f6df4 100644
--- a/x-pack/plugins/canvas/public/components/element_content/element_content.js
+++ b/x-pack/plugins/canvas/public/components/element_content/element_content.js
@@ -12,7 +12,6 @@ import { getType } from '@kbn/interpreter/common';
import { Loading } from '../loading';
import { RenderWithFn } from '../render_with_fn';
import { ElementShareContainer } from '../element_share_container';
-import { assignHandlers } from '../../lib/create_handlers';
import { InvalidExpression } from './invalid_expression';
import { InvalidElementType } from './invalid_element_type';
@@ -48,15 +47,7 @@ export const ElementContent = compose(
pure,
...branches
)(({ renderable, renderFunction, width, height, handlers }) => {
- const {
- getFilter,
- setFilter,
- done,
- onComplete,
- onEmbeddableInputChange,
- onEmbeddableDestroyed,
- getElementId,
- } = handlers;
+ const { onComplete } = handlers;
return Style.it(
renderable.css,
@@ -79,14 +70,7 @@ export const ElementContent = compose(
css={renderable.css} // This is an actual CSS stylesheet string, it will be scoped by RenderElement
width={width}
height={height}
- handlers={assignHandlers({
- getFilter,
- setFilter,
- done,
- onEmbeddableInputChange,
- onEmbeddableDestroyed,
- getElementId,
- })}
+ handlers={handlers}
/>
diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx
index c5fe7074fea0b..07d749c5677dc 100644
--- a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx
+++ b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx
@@ -6,6 +6,8 @@
import React, { useState, useEffect, useRef, FC, useCallback } from 'react';
+import { isEqual } from 'lodash';
+
import { useNotifyService } from '../../services';
import { RenderToDom } from '../render_to_dom';
import { ErrorStrings } from '../../../i18n';
@@ -82,8 +84,12 @@ export const RenderWithFn: FC = ({
);
const render = useCallback(() => {
+ if (!isEqual(handlers.current, incomingHandlers)) {
+ handlers.current = incomingHandlers;
+ }
+
renderFn(renderTarget.current!, config, handlers.current);
- }, [renderTarget, config, renderFn]);
+ }, [renderTarget, config, renderFn, incomingHandlers]);
useEffect(() => {
if (!domNode) {
diff --git a/x-pack/plugins/canvas/shareable_runtime/api/index.ts b/x-pack/plugins/canvas/shareable_runtime/api/index.ts
index 0780ab46cd873..dc7445eb7bc5a 100644
--- a/x-pack/plugins/canvas/shareable_runtime/api/index.ts
+++ b/x-pack/plugins/canvas/shareable_runtime/api/index.ts
@@ -7,5 +7,7 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import 'whatwg-fetch';
+import 'jquery';
+import '@kbn/ui-shared-deps/flot_charts';
export * from './shareable';
diff --git a/x-pack/plugins/cloud/README.md b/x-pack/plugins/cloud/README.md
new file mode 100644
index 0000000000000..13172e0a6ddc0
--- /dev/null
+++ b/x-pack/plugins/cloud/README.md
@@ -0,0 +1,3 @@
+# `cloud` plugin
+
+The `cloud` plugin adds cloud specific features to Kibana.
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts
new file mode 100644
index 0000000000000..ee5b47eda490e
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts
@@ -0,0 +1,58 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+// TODO: It's very likely that we'll move these i18n constants to their respective component
+// folders once those are migrated over. This is a temporary way of DRYing them out for now.
+
+export const ENGINES_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.engines.title', {
+ defaultMessage: 'Engines',
+});
+
+export const OVERVIEW_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.overview.title',
+ { defaultMessage: 'Overview' }
+);
+export const ANALYTICS_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.analytics.title',
+ { defaultMessage: 'Analytics' }
+);
+export const DOCUMENTS_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.documents.title',
+ { defaultMessage: 'Documents' }
+);
+export const SCHEMA_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.title', {
+ defaultMessage: 'Schema',
+});
+export const CRAWLER_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.crawler.title',
+ { defaultMessage: 'Crawler' }
+);
+export const RELEVANCE_TUNING_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.title',
+ { defaultMessage: 'Relevance Tuning' }
+);
+export const SYNONYMS_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.synonyms.title',
+ { defaultMessage: 'Synonyms' }
+);
+export const CURATIONS_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.curations.title',
+ { defaultMessage: 'Curations' }
+);
+export const RESULT_SETTINGS_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.resultSettings.title',
+ { defaultMessage: 'Result Settings' }
+);
+export const SEARCH_UI_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.searchUI.title',
+ { defaultMessage: 'Search UI' }
+);
+export const API_LOGS_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.apiLogs.title',
+ { defaultMessage: 'API Logs' }
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss
new file mode 100644
index 0000000000000..d7740724204a7
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+.appSearchNavEngineLabel {
+ padding-top: $euiSizeS;
+ padding-bottom: $euiSizeS;
+
+ .euiText {
+ font-weight: $euiFontWeightMedium;
+ }
+ .euiBadge {
+ margin-top: $euiSizeXS;
+ }
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx
new file mode 100644
index 0000000000000..7bdc3c86a50d6
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx
@@ -0,0 +1,138 @@
+/*
+ * 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 '../../../__mocks__/react_router_history.mock';
+import { setMockValues } from '../../../__mocks__/kea.mock';
+
+import React from 'react';
+import { shallow, mount } from 'enzyme';
+import { Switch, useParams } from 'react-router-dom';
+import { EuiBadge } from '@elastic/eui';
+
+import { EngineRouter, EngineNav } from './';
+
+describe('EngineRouter', () => {
+ it('renders a default engine overview', () => {
+ setMockValues({ myRole: {} });
+ const wrapper = shallow( );
+
+ expect(wrapper.find(Switch)).toHaveLength(1);
+ expect(wrapper.find('[data-test-subj="EngineOverviewTODO"]')).toHaveLength(1);
+ });
+
+ it('renders an analytics view', () => {
+ setMockValues({ myRole: { canViewEngineAnalytics: true } });
+ const wrapper = shallow( );
+
+ expect(wrapper.find('[data-test-subj="AnalyticsTODO"]')).toHaveLength(1);
+ });
+});
+
+describe('EngineNav', () => {
+ beforeEach(() => {
+ (useParams as jest.Mock).mockReturnValue({ engineName: 'some-engine' });
+ });
+
+ it('does not render without an engine name', () => {
+ setMockValues({ myRole: {} });
+ (useParams as jest.Mock).mockReturnValue({ engineName: '' });
+ const wrapper = shallow( );
+ expect(wrapper.isEmptyRender()).toBe(true);
+ });
+
+ it('renders an engine label', () => {
+ setMockValues({ myRole: {} });
+ const wrapper = mount( );
+
+ const label = wrapper.find('[data-test-subj="EngineLabel"]').last();
+ expect(label.text()).toEqual(expect.stringContaining('SOME-ENGINE'));
+
+ // TODO: Test sample & meta engine conditional rendering
+ expect(label.find(EuiBadge).text()).toEqual('SAMPLE ENGINE');
+ });
+
+ it('renders a default engine overview link', () => {
+ setMockValues({ myRole: {} });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineOverviewLink"]')).toHaveLength(1);
+ });
+
+ it('renders an analytics link', () => {
+ setMockValues({ myRole: { canViewEngineAnalytics: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineAnalyticsLink"]')).toHaveLength(1);
+ });
+
+ it('renders a documents link', () => {
+ setMockValues({ myRole: { canViewEngineDocuments: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineDocumentsLink"]')).toHaveLength(1);
+ });
+
+ it('renders a schema link', () => {
+ setMockValues({ myRole: { canViewEngineSchema: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineSchemaLink"]')).toHaveLength(1);
+
+ // TODO: Schema warning icon
+ });
+
+ // TODO: Unskip when EngineLogic is migrated
+ it.skip('renders a crawler link', () => {
+ setMockValues({ myRole: { canViewEngineCrawler: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(1);
+
+ // TODO: Test that the crawler link does NOT show up for meta/sample engines
+ });
+
+ // TODO: Unskip when EngineLogic is migrated
+ it.skip('renders a meta engine source engines link', () => {
+ setMockValues({ myRole: { canViewMetaEngineSourceEngines: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="MetaEngineEnginesLink"]')).toHaveLength(1);
+
+ // TODO: Test that the crawler link does NOT show up for non-meta engines
+ });
+
+ it('renders a relevance tuning link', () => {
+ setMockValues({ myRole: { canManageEngineRelevanceTuning: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineRelevanceTuningLink"]')).toHaveLength(1);
+
+ // TODO: Boost error icon
+ });
+
+ it('renders a synonyms link', () => {
+ setMockValues({ myRole: { canManageEngineSynonyms: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineSynonymsLink"]')).toHaveLength(1);
+ });
+
+ it('renders a curations link', () => {
+ setMockValues({ myRole: { canManageEngineCurations: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineCurationsLink"]')).toHaveLength(1);
+ });
+
+ it('renders a results settings link', () => {
+ setMockValues({ myRole: { canManageEngineResultSettings: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineResultSettingsLink"]')).toHaveLength(1);
+ });
+
+ it('renders a Search UI link', () => {
+ setMockValues({ myRole: { canManageEngineSearchUi: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineSearchUILink"]')).toHaveLength(1);
+ });
+
+ it('renders an API logs link', () => {
+ setMockValues({ myRole: { canViewEngineApiLogs: true } });
+ const wrapper = shallow( );
+ expect(wrapper.find('[data-test-subj="EngineAPILogsLink"]')).toHaveLength(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx
new file mode 100644
index 0000000000000..e5ee392b34c01
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx
@@ -0,0 +1,225 @@
+/*
+ * 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 React from 'react';
+import { Route, Switch, useParams } from 'react-router-dom';
+import { useValues } from 'kea';
+
+import { EuiText, EuiBadge } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+import { SideNavLink, SideNavItem } from '../../../shared/layout';
+import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
+import { AppLogic } from '../../app_logic';
+import {
+ getEngineRoute,
+ ENGINE_PATH,
+ ENGINE_ANALYTICS_PATH,
+ ENGINE_DOCUMENTS_PATH,
+ ENGINE_SCHEMA_PATH,
+ ENGINE_CRAWLER_PATH,
+ META_ENGINE_SOURCE_ENGINES_PATH,
+ ENGINE_RELEVANCE_TUNING_PATH,
+ ENGINE_SYNONYMS_PATH,
+ ENGINE_CURATIONS_PATH,
+ ENGINE_RESULT_SETTINGS_PATH,
+ ENGINE_SEARCH_UI_PATH,
+ ENGINE_API_LOGS_PATH,
+} from '../../routes';
+import { getAppSearchUrl } from '../../../shared/enterprise_search_url';
+import {
+ ENGINES_TITLE,
+ OVERVIEW_TITLE,
+ ANALYTICS_TITLE,
+ DOCUMENTS_TITLE,
+ SCHEMA_TITLE,
+ CRAWLER_TITLE,
+ RELEVANCE_TUNING_TITLE,
+ SYNONYMS_TITLE,
+ CURATIONS_TITLE,
+ RESULT_SETTINGS_TITLE,
+ SEARCH_UI_TITLE,
+ API_LOGS_TITLE,
+} from './constants';
+
+import './engine_nav.scss';
+
+export const EngineRouter: React.FC = () => {
+ const {
+ myRole: { canViewEngineAnalytics },
+ } = useValues(AppLogic);
+
+ // TODO: EngineLogic
+
+ const { engineName } = useParams() as { engineName: string };
+ const engineBreadcrumb = [ENGINES_TITLE, engineName];
+
+ return (
+ // TODO: Add more routes as we migrate them
+
+ {canViewEngineAnalytics && (
+
+
+ Just testing right now
+
+ )}
+
+
+ Overview
+
+
+ );
+};
+
+export const EngineNav: React.FC = () => {
+ const {
+ myRole: {
+ canViewEngineAnalytics,
+ canViewEngineDocuments,
+ canViewEngineSchema,
+ canViewEngineCrawler,
+ canViewMetaEngineSourceEngines,
+ canManageEngineSynonyms,
+ canManageEngineCurations,
+ canManageEngineRelevanceTuning,
+ canManageEngineResultSettings,
+ canManageEngineSearchUi,
+ canViewEngineApiLogs,
+ },
+ } = useValues(AppLogic);
+
+ // TODO: Use EngineLogic
+ const isSampleEngine = true;
+ const isMetaEngine = false;
+ const { engineName } = useParams() as { engineName: string };
+ const engineRoute = engineName && getEngineRoute(engineName);
+
+ if (!engineName) return null;
+
+ return (
+ <>
+
+
+ {engineName.toUpperCase()}
+ {isSampleEngine && (
+
+ {i18n.translate('xpack.enterpriseSearch.appSearch.engine.sampleEngineBadge', {
+ defaultMessage: 'SAMPLE ENGINE',
+ })}
+
+ )}
+ {isMetaEngine && (
+
+ {i18n.translate('xpack.enterpriseSearch.appSearch.engine.metaEngineBadge', {
+ defaultMessage: 'META ENGINE',
+ })}
+
+ )}
+
+
+
+ {OVERVIEW_TITLE}
+
+ {canViewEngineAnalytics && (
+
+ {ANALYTICS_TITLE}
+
+ )}
+ {canViewEngineDocuments && (
+
+ {DOCUMENTS_TITLE}
+
+ )}
+ {canViewEngineSchema && (
+
+ {SCHEMA_TITLE}
+ {/* TODO: Engine schema warning icon */}
+
+ )}
+ {canViewEngineCrawler && !isMetaEngine && !isSampleEngine && (
+
+ {CRAWLER_TITLE}
+
+ )}
+ {canViewMetaEngineSourceEngines && isMetaEngine && (
+
+ {ENGINES_TITLE}
+
+ )}
+ {canManageEngineRelevanceTuning && (
+
+ {RELEVANCE_TUNING_TITLE}
+ {/* TODO: invalid boosts error icon */}
+
+ )}
+ {canManageEngineSynonyms && (
+
+ {SYNONYMS_TITLE}
+
+ )}
+ {canManageEngineCurations && (
+
+ {CURATIONS_TITLE}
+
+ )}
+ {canManageEngineResultSettings && (
+
+ {RESULT_SETTINGS_TITLE}
+
+ )}
+ {canManageEngineSearchUi && (
+
+ {SEARCH_UI_TITLE}
+
+ )}
+ {canViewEngineApiLogs && (
+
+ {API_LOGS_TITLE}
+
+ )}
+ >
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts
new file mode 100644
index 0000000000000..a3320ba5024ca
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export { EngineRouter, EngineNav } from './engine_nav';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx
index 37ed45a379c0e..a4f248f87a81c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx
@@ -9,7 +9,8 @@ import '../../../__mocks__/enterprise_search_url.mock';
import { mockTelemetryActions, mountWithIntl } from '../../../__mocks__/';
import React from 'react';
-import { EuiBasicTable, EuiPagination, EuiButtonEmpty, EuiLink } from '@elastic/eui';
+import { EuiBasicTable, EuiPagination, EuiButtonEmpty } from '@elastic/eui';
+import { EuiLink } from '../../../shared/react_router_helpers';
import { EngineTable } from './engine_table';
@@ -52,7 +53,7 @@ describe('EngineTable', () => {
const engineLinks = wrapper.find(EuiLink);
engineLinks.forEach((link) => {
- expect(link.prop('href')).toEqual('http://localhost:3002/as/engines/test-engine');
+ expect(link.prop('to')).toEqual('/engines/test-engine');
link.simulate('click');
expect(mockTelemetryActions.sendAppSearchTelemetry).toHaveBeenCalledWith({
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx
index ffa5b8e9a1622..abeaf45e6aee8 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx
@@ -6,12 +6,12 @@
import React from 'react';
import { useActions } from 'kea';
-import { EuiBasicTable, EuiBasicTableColumn, EuiLink } from '@elastic/eui';
+import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui';
import { FormattedMessage, FormattedDate, FormattedNumber } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { TelemetryLogic } from '../../../shared/telemetry';
-import { getAppSearchUrl } from '../../../shared/enterprise_search_url';
+import { EuiLink } from '../../../shared/react_router_helpers';
import { getEngineRoute } from '../../routes';
import { ENGINES_PAGE_SIZE } from '../../../../../common/constants';
@@ -44,8 +44,7 @@ export const EngineTable: React.FC = ({
const { sendAppSearchTelemetry } = useActions(TelemetryLogic);
const engineLinkProps = (name: string) => ({
- href: getAppSearchUrl(getEngineRoute(name)),
- target: '_blank',
+ to: getEngineRoute(name),
onClick: () =>
sendAppSearchTelemetry({
action: 'clicked',
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
index 49e74582f5f15..700b903efe59b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
@@ -17,6 +17,7 @@ import { Layout, SideNav, SideNavLink } from '../shared/layout';
import { SetupGuide } from './components/setup_guide';
import { ErrorConnecting } from './components/error_connecting';
import { EngineOverview } from './components/engine_overview';
+import { EngineRouter } from './components/engine';
import { AppSearch, AppSearchUnconfigured, AppSearchConfigured, AppSearchNav } from './';
describe('AppSearch', () => {
@@ -54,9 +55,10 @@ describe('AppSearchConfigured', () => {
it('renders with layout', () => {
const wrapper = shallow( );
- expect(wrapper.find(Layout)).toHaveLength(1);
- expect(wrapper.find(Layout).prop('readOnlyMode')).toBeFalsy();
+ expect(wrapper.find(Layout)).toHaveLength(2);
+ expect(wrapper.find(Layout).last().prop('readOnlyMode')).toBeFalsy();
expect(wrapper.find(EngineOverview)).toHaveLength(1);
+ expect(wrapper.find(EngineRouter)).toHaveLength(1);
});
it('initializes app data with passed props', () => {
@@ -91,7 +93,7 @@ describe('AppSearchConfigured', () => {
const wrapper = shallow( );
- expect(wrapper.find(Layout).prop('readOnlyMode')).toEqual(true);
+ expect(wrapper.find(Layout).first().prop('readOnlyMode')).toEqual(true);
});
describe('ability checks', () => {
@@ -108,6 +110,13 @@ describe('AppSearchNav', () => {
expect(wrapper.find(SideNavLink).prop('to')).toEqual('/engines');
});
+ it('renders an Engine subnav if passed', () => {
+ const wrapper = shallow(Testing} />);
+ const link = wrapper.find(SideNavLink).dive();
+
+ expect(link.find('[data-test-subj="subnav"]')).toHaveLength(1);
+ });
+
it('renders the Settings link', () => {
setMockValues({ myRole: { canViewSettings: true } });
const wrapper = shallow( );
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
index cf67aa3ec7d9d..f32b0b256b898 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
@@ -18,6 +18,7 @@ import { IInitialAppData } from '../../../common/types';
import { APP_SEARCH_PLUGIN } from '../../../common/constants';
import { Layout, SideNav, SideNavLink } from '../shared/layout';
+import { EngineNav, EngineRouter } from './components/engine';
import {
ROOT_PATH,
@@ -26,6 +27,7 @@ import {
CREDENTIALS_PATH,
ROLE_MAPPINGS_PATH,
ENGINES_PATH,
+ ENGINE_PATH,
} from './routes';
import { SetupGuide } from './components/setup_guide';
@@ -65,6 +67,11 @@ export const AppSearchConfigured: React.FC = (props) => {
+
+ } />} readOnlyMode={readOnlyMode}>
+
+
+
} readOnlyMode={readOnlyMode}>
{errorConnecting ? (
@@ -94,14 +101,18 @@ export const AppSearchConfigured: React.FC = (props) => {
);
};
-export const AppSearchNav: React.FC = () => {
+interface IAppSearchNavProps {
+ subNav?: React.ReactNode;
+}
+
+export const AppSearchNav: React.FC = ({ subNav }) => {
const {
myRole: { canViewSettings, canViewAccountCredentials, canViewRoleMappings },
} = useValues(AppLogic);
return (
-
+
{i18n.translate('xpack.enterpriseSearch.appSearch.nav.engines', {
defaultMessage: 'Engines',
})}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
index 51e2497365dd7..3f2d5a7f8ab84 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
@@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { generatePath } from 'react-router-dom';
+
export const ROOT_PATH = '/';
export const SETUP_GUIDE_PATH = '/setup_guide';
export const SETTINGS_PATH = '/settings/account';
@@ -14,4 +16,28 @@ export const ENGINES_PATH = '/engines';
export const CREATE_ENGINES_PATH = `${ENGINES_PATH}/new`;
export const ENGINE_PATH = '/engines/:engineName';
-export const getEngineRoute = (engineName: string) => `${ENGINES_PATH}/${engineName}`;
+export const SAMPLE_ENGINE_PATH = '/engines/national-parks-demo';
+export const getEngineRoute = (engineName: string) => generatePath(ENGINE_PATH, { engineName });
+
+export const ENGINE_ANALYTICS_PATH = '/analytics';
+// TODO: Analytics sub-pages
+
+export const ENGINE_DOCUMENTS_PATH = '/documents';
+export const ENGINE_DOCUMENT_DETAIL_PATH = `${ENGINE_DOCUMENTS_PATH}/:documentId`;
+
+export const ENGINE_SCHEMA_PATH = '/schema/edit';
+export const ENGINE_REINDEX_JOB_PATH = '/reindex-job/:activeReindexJobId';
+
+export const ENGINE_CRAWLER_PATH = '/crawler';
+// TODO: Crawler sub-pages
+
+export const META_ENGINE_SOURCE_ENGINES_PATH = '/engines';
+
+export const ENGINE_RELEVANCE_TUNING_PATH = '/search-settings';
+export const ENGINE_SYNONYMS_PATH = '/synonyms';
+export const ENGINE_CURATIONS_PATH = '/curations';
+// TODO: Curations sub-pages
+export const ENGINE_RESULT_SETTINGS_PATH = '/result-settings';
+
+export const ENGINE_SEARCH_UI_PATH = '/reference_application/new';
+export const ENGINE_API_LOGS_PATH = '/api-logs';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.test.ts
index a2eb3d8fbc90d..71b8d90d46e43 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.test.ts
@@ -43,7 +43,7 @@ describe('getRoleAbilities', () => {
canManageEngineCredentials: false,
canManageEngineCurations: false,
canManageEngineRelevanceTuning: false,
- canManageEngineReferenceUi: false,
+ canManageEngineSearchUi: false,
canManageEngineResultSettings: false,
canManageEngineSchema: false,
canManageMetaEngineSourceEngines: false,
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts
index 409aef3cd42ec..a935fa657738c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts
@@ -38,9 +38,9 @@ export interface IRole {
canManageEngineCredentials: boolean;
canManageEngineCurations: boolean;
canManageEngineRelevanceTuning: boolean;
- canManageEngineReferenceUi: boolean;
canManageEngineResultSettings: boolean;
canManageEngineSchema: boolean;
+ canManageEngineSearchUi: boolean;
canManageMetaEngineSourceEngines: boolean;
}
@@ -94,9 +94,9 @@ export const getRoleAbilities = (role: IAccount['role']): IRole => {
canManageEngineCredentials: myRole.can('manage', 'engine_credentials'),
canManageEngineCurations: myRole.can('manage', 'engine_curations'),
canManageEngineRelevanceTuning: myRole.can('manage', 'engine_relevance_tuning'),
- canManageEngineReferenceUi: myRole.can('manage', 'engine_reference_ui'),
canManageEngineResultSettings: myRole.can('manage', 'engine_result_settings'),
canManageEngineSchema: myRole.can('manage', 'engine_schema'),
+ canManageEngineSearchUi: myRole.can('manage', 'engine_reference_ui'),
canManageMetaEngineSourceEngines: myRole.can('manage', 'meta_engine_source_engines'),
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts
index 2211cdee6c730..d522bd56c46a0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/index.ts
@@ -5,4 +5,4 @@
*/
export { Layout } from './layout';
-export { SideNav, SideNavLink } from './side_nav';
+export { SideNav, SideNavLink, SideNavItem } from './side_nav';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx
index b006068ac0d9e..e3e9872f892a4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx
@@ -14,7 +14,7 @@ import { EuiLink as EuiLinkExternal } from '@elastic/eui';
import { EuiLink } from '../react_router_helpers';
import { ENTERPRISE_SEARCH_PLUGIN, APP_SEARCH_PLUGIN } from '../../../../common/constants';
-import { SideNav, SideNavLink } from './';
+import { SideNav, SideNavLink, SideNavItem } from './';
describe('SideNav', () => {
it('renders link children', () => {
@@ -106,3 +106,33 @@ describe('SideNavLink', () => {
expect(wrapper.find('[data-test-subj="subNav"]')).toHaveLength(1);
});
});
+
+describe('SideNavItem', () => {
+ it('renders', () => {
+ const wrapper = shallow(Test );
+
+ expect(wrapper.type()).toEqual('li');
+ expect(wrapper.find('.enterpriseSearchNavLinks__item')).toHaveLength(1);
+ });
+
+ it('renders children', () => {
+ const wrapper = shallow(
+
+ World
+
+ );
+
+ expect(wrapper.find('[data-test-subj="hello"]').text()).toEqual('World');
+ });
+
+ it('passes down custom classes and props', () => {
+ const wrapper = shallow(
+
+ Test
+
+ );
+
+ expect(wrapper.find('.testing')).toHaveLength(1);
+ expect(wrapper.find('[data-test-subj="testing"]')).toHaveLength(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx
index 837a565d5525d..facfd0bfcb16d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx
@@ -109,3 +109,20 @@ export const SideNavLink: React.FC = ({
);
};
+
+/**
+ * Side navigation non-link item
+ */
+
+interface ISideNavItemProps {
+ className?: string;
+}
+
+export const SideNavItem: React.FC = ({ children, className, ...rest }) => {
+ const classes = classNames('enterpriseSearchNavLinks__item', className);
+ return (
+
+ {children}
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
index a454e3146f4d9..6fa6698e6b6ba 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
-import { i18n } from '@kbn/i18n';
import { EuiSpacer } from '@elastic/eui';
@@ -13,7 +12,7 @@ import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url';
import { SideNav, SideNavLink } from '../../../shared/layout';
import { GroupSubNav } from '../../views/groups/components/group_sub_nav';
-import { NAV } from '../../views/groups/constants';
+import { NAV } from '../../constants';
import {
ORG_SOURCES_PATH,
@@ -29,38 +28,26 @@ export const WorkplaceSearchNav: React.FC = () => {
return (
- {i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.overview', {
- defaultMessage: 'Overview',
- })}
+ {NAV.OVERVIEW}
- {i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.sources', {
- defaultMessage: 'Sources',
- })}
+ {NAV.SOURCES}
}>
{NAV.GROUPS}
- {i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.roleMappings', {
- defaultMessage: 'Role Mappings',
- })}
+ {NAV.ROLE_MAPPINGS}
- {i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.security', {
- defaultMessage: 'Security',
- })}
+ {NAV.SECURITY}
- {i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.settings', {
- defaultMessage: 'Settings',
- })}
+ {NAV.SETTINGS}
- {i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.personalDashboard', {
- defaultMessage: 'View my personal dashboard',
- })}
+ {NAV.PERSONAL_DASHBOARD}
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_config_fields/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_config_fields/index.ts
new file mode 100644
index 0000000000000..d857ed5775d81
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_config_fields/index.ts
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export { SourceConfigFields } from './source_config_fields';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_config_fields/source_config_fields.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_config_fields/source_config_fields.test.tsx
new file mode 100644
index 0000000000000..c9565d75e176b
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_config_fields/source_config_fields.test.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { ApiKey } from '../api_key';
+import { CredentialItem } from '../credential_item';
+
+import { SourceConfigFields } from './';
+
+describe('SourceConfigFields', () => {
+ it('renders empty with no items', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(ApiKey)).toHaveLength(0);
+ expect(wrapper.find(CredentialItem)).toHaveLength(0);
+ });
+
+ it('renders with all items, hiding API Keys', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.find(ApiKey)).toHaveLength(0);
+ expect(wrapper.find(CredentialItem)).toHaveLength(3);
+ });
+
+ it('shows API keys', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.find(ApiKey)).toHaveLength(2);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_config_fields/source_config_fields.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_config_fields/source_config_fields.tsx
new file mode 100644
index 0000000000000..c0249afaeb3cc
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_config_fields/source_config_fields.tsx
@@ -0,0 +1,61 @@
+/*
+ * 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 React from 'react';
+
+import { EuiSpacer } from '@elastic/eui';
+
+import { ApiKey } from '../api_key';
+import { CredentialItem } from '../credential_item';
+
+interface ISourceConfigFieldsProps {
+ clientId?: string;
+ clientSecret?: string;
+ publicKey?: string;
+ consumerKey?: string;
+ baseUrl?: string;
+}
+
+export const SourceConfigFields: React.FC = ({
+ clientId,
+ clientSecret,
+ publicKey,
+ consumerKey,
+ baseUrl,
+}) => {
+ const showApiKey = (publicKey || consumerKey) && !clientId;
+
+ const credentialItem = (label: string, item?: string) =>
+ item && ;
+
+ const keyElement = (
+ <>
+ {publicKey && (
+ <>
+
+
+ >
+ )}
+ {consumerKey && (
+ <>
+
+
+ >
+ )}
+ >
+ );
+
+ return (
+ <>
+ {showApiKey && keyElement}
+ {credentialItem('Client id', clientId)}
+
+ {credentialItem('Client secret', clientSecret)}
+
+ {credentialItem('Base URL', baseUrl)}
+ >
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
index 9f313a6995ad5..3b911b87dea12 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
@@ -3,6 +3,44 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import { i18n } from '@kbn/i18n';
+
+export const NAV = {
+ OVERVIEW: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.overview', {
+ defaultMessage: 'Overview',
+ }),
+ SOURCES: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.sources', {
+ defaultMessage: 'Sources',
+ }),
+ GROUPS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.groups', {
+ defaultMessage: 'Groups',
+ }),
+ GROUP_OVERVIEW: i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.nav.groups.groupOverview',
+ {
+ defaultMessage: 'Overview',
+ }
+ ),
+ SOURCE_PRIORITIZATION: i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.nav.groups.sourcePrioritization',
+ { defaultMessage: 'Source Prioritization' }
+ ),
+ ROLE_MAPPINGS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.roleMappings', {
+ defaultMessage: 'Role Mappings',
+ }),
+ SECURITY: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.security', {
+ defaultMessage: 'Security',
+ }),
+ SETTINGS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.settings', {
+ defaultMessage: 'Settings',
+ }),
+ PERSONAL_DASHBOARD: i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.nav.personalDashboard',
+ {
+ defaultMessage: 'View my personal dashboard',
+ }
+ ),
+};
export const MAX_TABLE_ROW_ICONS = 3;
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
index e22b9c6282f95..9875af7889447 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
@@ -16,7 +16,7 @@ import { AppLogic } from './app_logic';
import { Layout } from '../shared/layout';
import { WorkplaceSearchNav, WorkplaceSearchHeaderActions } from './components/layout';
-import { SETUP_GUIDE_PATH } from './routes';
+import { GROUPS_PATH, SETUP_GUIDE_PATH } from './routes';
import { SetupGuide } from './views/setup_guide';
import { ErrorState } from './views/error_state';
@@ -56,7 +56,7 @@ export const WorkplaceSearchConfigured: React.FC = (props) => {
) : (
-
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sub_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sub_nav.tsx
index a41cf6191eb64..ad19ad059fb40 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sub_nav.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sub_nav.tsx
@@ -8,7 +8,7 @@ import React from 'react';
import { useValues } from 'kea';
import { GroupLogic } from '../group_logic';
-import { NAV } from '../constants';
+import { NAV } from '../../../constants';
import { SideNavLink } from '../../../../shared/layout';
@@ -23,7 +23,7 @@ export const GroupSubNav: React.FC = () => {
return (
<>
- {NAV.OVERVIEW}
+ {NAV.GROUP_OVERVIEW}
{NAV.SOURCE_PRIORITIZATION}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/constants.ts
deleted file mode 100644
index 7c3d160017138..0000000000000
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/constants.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { i18n } from '@kbn/i18n';
-
-export const NAV = {
- GROUPS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.groups', {
- defaultMessage: 'Groups',
- }),
- OVERVIEW: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.groups.groupOverview', {
- defaultMessage: 'Overview',
- }),
- SOURCE_PRIORITIZATION: i18n.translate(
- 'xpack.enterpriseSearch.workplaceSearch.nav.groups.sourcePrioritization',
- { defaultMessage: 'Source Prioritization' }
- ),
-};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx
index 1b6f0c4c49a05..822d966bfb8d2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx
@@ -14,7 +14,7 @@ import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kiban
import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
import { GROUP_SOURCE_PRIORITIZATION_PATH, GROUP_PATH } from '../../routes';
-import { NAV } from './constants';
+import { NAV } from '../../constants';
import { GroupLogic } from './group_logic';
import { ManageUsersModal } from './components/manage_users_modal';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_router.tsx
index a4fe472065d90..326362a0ddfbc 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_router.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_router.tsx
@@ -14,7 +14,7 @@ import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kiban
import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
import { GROUP_PATH, GROUPS_PATH } from '../../routes';
-import { NAV } from './constants';
+import { NAV } from '../../constants';
import { GroupsLogic } from './groups_logic';
diff --git a/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts b/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts
index e3471d7268cb1..f00e0f2807e8d 100644
--- a/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts
+++ b/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts
@@ -21,6 +21,7 @@ type PayloadType = 'params' | 'query' | 'body';
interface IMockRouterProps {
method: MethodType;
+ path: string;
payload?: PayloadType;
}
interface IMockRouterRequest {
@@ -33,12 +34,14 @@ type TMockRouterRequest = KibanaRequest | IMockRouterRequest;
export class MockRouter {
public router!: jest.Mocked;
public method: MethodType;
+ public path: string;
public payload?: PayloadType;
public response = httpServerMock.createResponseFactory();
- constructor({ method, payload }: IMockRouterProps) {
+ constructor({ method, path, payload }: IMockRouterProps) {
this.createRouter();
this.method = method;
+ this.path = path;
this.payload = payload;
}
@@ -47,8 +50,13 @@ export class MockRouter {
};
public callRoute = async (request: TMockRouterRequest) => {
- const [, handler] = this.router[this.method].mock.calls[0];
+ const routerCalls = this.router[this.method].mock.calls as any[];
+ if (!routerCalls.length) throw new Error('No routes registered.');
+ const route = routerCalls.find(([router]: any) => router.path === this.path);
+ if (!route) throw new Error('No matching registered routes found - check method/path keys');
+
+ const [, handler] = route;
const context = {} as jest.Mocked;
await handler(context, httpServerMock.createKibanaRequest(request as any), this.response);
};
@@ -81,7 +89,11 @@ export class MockRouter {
/**
* Example usage:
*/
-// const mockRouter = new MockRouter({ method: 'get', payload: 'body' });
+// const mockRouter = new MockRouter({
+// method: 'get',
+// path: '/api/app_search/test',
+// payload: 'body'
+// });
//
// beforeEach(() => {
// jest.clearAllMocks();
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts
index 357b49de93412..af498e346529a 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/credentials.test.ts
@@ -14,7 +14,11 @@ describe('credentials routes', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockRouter = new MockRouter({ method: 'get', payload: 'query' });
+ mockRouter = new MockRouter({
+ method: 'get',
+ path: '/api/app_search/credentials',
+ payload: 'query',
+ });
registerCredentialsRoutes({
...mockDependencies,
@@ -46,7 +50,11 @@ describe('credentials routes', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockRouter = new MockRouter({ method: 'post', payload: 'body' });
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/api/app_search/credentials',
+ payload: 'body',
+ });
registerCredentialsRoutes({
...mockDependencies,
@@ -155,7 +163,11 @@ describe('credentials routes', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockRouter = new MockRouter({ method: 'get', payload: 'query' });
+ mockRouter = new MockRouter({
+ method: 'get',
+ path: '/api/app_search/credentials/details',
+ payload: 'query',
+ });
registerCredentialsRoutes({
...mockDependencies,
@@ -175,7 +187,11 @@ describe('credentials routes', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockRouter = new MockRouter({ method: 'put', payload: 'body' });
+ mockRouter = new MockRouter({
+ method: 'put',
+ path: '/api/app_search/credentials/{name}',
+ payload: 'body',
+ });
registerCredentialsRoutes({
...mockDependencies,
@@ -292,7 +308,11 @@ describe('credentials routes', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockRouter = new MockRouter({ method: 'delete', payload: 'params' });
+ mockRouter = new MockRouter({
+ method: 'delete',
+ path: '/api/app_search/credentials/{name}',
+ payload: 'params',
+ });
registerCredentialsRoutes({
...mockDependencies,
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts
index cd22ff98b01ce..3bfe8abf8a2df 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts
@@ -25,7 +25,11 @@ describe('engine routes', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockRouter = new MockRouter({ method: 'get', payload: 'query' });
+ mockRouter = new MockRouter({
+ method: 'get',
+ path: '/api/app_search/engines',
+ payload: 'query',
+ });
registerEnginesRoute({
...mockDependencies,
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts
index 095c0ac2b6ab1..be3b2632eb67d 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/settings.test.ts
@@ -14,7 +14,10 @@ describe('log settings routes', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockRouter = new MockRouter({ method: 'get' });
+ mockRouter = new MockRouter({
+ method: 'get',
+ path: '/api/app_search/log_settings',
+ });
registerSettingsRoutes({
...mockDependencies,
@@ -36,7 +39,11 @@ describe('log settings routes', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockRouter = new MockRouter({ method: 'put', payload: 'body' });
+ mockRouter = new MockRouter({
+ method: 'put',
+ path: '/api/app_search/log_settings',
+ payload: 'body',
+ });
registerSettingsRoutes({
...mockDependencies,
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.test.ts
index 253c9a418d60b..b6f449ced2599 100644
--- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.test.ts
@@ -18,7 +18,10 @@ describe('Enterprise Search Config Data API', () => {
let mockRouter: MockRouter;
beforeEach(() => {
- mockRouter = new MockRouter({ method: 'get' });
+ mockRouter = new MockRouter({
+ method: 'get',
+ path: '/api/enterprise_search/config_data',
+ });
registerConfigDataRoute({
...mockDependencies,
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts
index bd6f4b9da91fd..2229860d87a00 100644
--- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts
@@ -25,7 +25,11 @@ describe('Enterprise Search Telemetry API', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockRouter = new MockRouter({ method: 'put', payload: 'body' });
+ mockRouter = new MockRouter({
+ method: 'put',
+ path: '/api/enterprise_search/stats',
+ payload: 'body',
+ });
registerTelemetryRoute({
...mockDependencies,
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts
new file mode 100644
index 0000000000000..2f244022be037
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.test.ts
@@ -0,0 +1,395 @@
+/*
+ * 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 { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__';
+
+import {
+ registerGroupsRoute,
+ registerSearchGroupsRoute,
+ registerGroupRoute,
+ registerGroupUsersRoute,
+ registerShareGroupRoute,
+ registerAssignGroupRoute,
+ registerBoostsGroupRoute,
+} from './groups';
+
+describe('groups routes', () => {
+ describe('GET /api/workplace_search/groups', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('creates a request handler', () => {
+ mockRouter = new MockRouter({
+ method: 'get',
+ path: '/api/workplace_search/groups',
+ payload: 'query',
+ });
+
+ registerGroupsRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/groups',
+ });
+ });
+ });
+
+ describe('POST /api/workplace_search/groups', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/api/workplace_search/groups',
+ payload: 'body',
+ });
+
+ registerGroupsRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request handler', () => {
+ const mockRequest = {
+ body: {
+ group_name: 'group',
+ },
+ };
+ mockRouter.callRoute(mockRequest);
+
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/groups',
+ ...mockRequest,
+ });
+ });
+ });
+
+ describe('POST /api/workplace_search/groups/search', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/api/workplace_search/groups/search',
+ payload: 'body',
+ });
+
+ registerSearchGroupsRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request handler', () => {
+ const mockRequest = {
+ body: {
+ page: {
+ current: 1,
+ size: 1,
+ },
+ search: {
+ query: 'foo',
+ content_source_ids: ['123', '234'],
+ user_ids: ['345', '456'],
+ },
+ },
+ };
+ mockRouter.callRoute(mockRequest);
+
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/groups/search',
+ ...mockRequest,
+ });
+ });
+
+ describe('validates', () => {
+ it('correctly', () => {
+ const request = {
+ body: {
+ page: {
+ current: 1,
+ size: 1,
+ },
+ search: {
+ query: 'foo',
+ content_source_ids: ['123', '234'],
+ user_ids: ['345', '456'],
+ },
+ },
+ };
+ mockRouter.shouldValidate(request);
+ });
+
+ it('throws on unnecessary properties', () => {
+ const request = {
+ body: {
+ page: null,
+ search: {
+ kites: 'bar',
+ },
+ },
+ };
+ mockRouter.shouldThrow(request);
+ });
+ });
+ });
+
+ describe('GET /api/workplace_search/groups/{id}', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('creates a request handler', () => {
+ mockRouter = new MockRouter({
+ method: 'get',
+ path: '/api/workplace_search/groups/{id}',
+ payload: 'params',
+ });
+
+ registerGroupRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+
+ const mockRequest = {
+ params: {
+ id: '123',
+ },
+ };
+
+ mockRouter.callRoute(mockRequest);
+
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/groups/123',
+ });
+ });
+ });
+
+ describe('PUT /api/workplace_search/groups/{id}', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ const mockPayload = {
+ group: {
+ name: 'group',
+ },
+ };
+
+ it('creates a request handler', () => {
+ mockRouter = new MockRouter({
+ method: 'put',
+ path: '/api/workplace_search/groups/{id}',
+ payload: 'body',
+ });
+
+ registerGroupRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+
+ const mockRequest = {
+ params: {
+ id: '123',
+ },
+ body: mockPayload,
+ };
+
+ mockRouter.callRoute(mockRequest);
+
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/groups/123',
+ body: mockPayload,
+ });
+ });
+ });
+
+ describe('DELETE /api/workplace_search/groups/{id}', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'delete',
+ path: '/api/workplace_search/groups/{id}',
+ payload: 'params',
+ });
+
+ registerGroupRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request handler', () => {
+ const mockRequest = {
+ params: {
+ id: '123',
+ },
+ };
+
+ mockRouter.callRoute(mockRequest);
+
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/groups/123',
+ });
+ });
+ });
+
+ describe('GET /api/workplace_search/groups/{id}/group_users', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('creates a request handler', () => {
+ mockRouter = new MockRouter({
+ method: 'get',
+ path: '/api/workplace_search/groups/{id}/group_users',
+ payload: 'params',
+ });
+
+ registerGroupUsersRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+
+ const mockRequest = {
+ params: {
+ id: '123',
+ },
+ };
+
+ mockRouter.callRoute(mockRequest);
+
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/groups/123/group_users',
+ });
+ });
+ });
+
+ describe('POST /api/workplace_search/groups/{id}/share', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/api/workplace_search/groups/{id}/share',
+ payload: 'body',
+ });
+
+ registerShareGroupRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request handler', () => {
+ const mockRequest = {
+ params: { id: '123' },
+ body: {
+ content_source_ids: ['123', '234'],
+ },
+ };
+
+ mockRouter.callRoute(mockRequest);
+
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/groups/123/share',
+ body: mockRequest.body,
+ });
+ });
+ });
+
+ describe('POST /api/workplace_search/groups/{id}/assign', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/api/workplace_search/groups/{id}/assign',
+ payload: 'body',
+ });
+
+ registerAssignGroupRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request handler', () => {
+ const mockRequest = {
+ params: { id: '123' },
+ body: {
+ user_ids: ['123', '234'],
+ },
+ };
+
+ mockRouter.callRoute(mockRequest);
+
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/groups/123/assign',
+ body: mockRequest.body,
+ });
+ });
+ });
+
+ describe('PUT /api/workplace_search/groups/{id}/boosts', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ const mockPayload = {
+ group: {
+ content_source_boosts: [['boost'], ['boost2', 'boost3']],
+ },
+ };
+
+ it('creates a request handler', () => {
+ mockRouter = new MockRouter({
+ method: 'put',
+ path: '/api/workplace_search/groups/{id}/boosts',
+ payload: 'body',
+ });
+
+ registerBoostsGroupRoute({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+
+ const mockRequest = {
+ params: {
+ id: '123',
+ },
+ body: mockPayload,
+ };
+
+ mockRouter.callRoute(mockRequest);
+
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/ws/org/groups/123/update_source_boosts',
+ body: mockPayload,
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts
index cbb78cef5b66c..35c585eb9f781 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts
@@ -8,9 +8,6 @@ import { schema } from '@kbn/config-schema';
import { IRouteDependencies } from '../../plugin';
-import { IMeta } from '../../../common/types';
-import { IUser, IContentSource, IGroup } from '../../../common/types/workplace_search';
-
export function registerGroupsRoute({
router,
enterpriseSearchRequestHandler,
@@ -22,8 +19,6 @@ export function registerGroupsRoute({
},
enterpriseSearchRequestHandler.createRequest({
path: '/ws/org/groups',
- hasValidData: (body: { users: IUser[]; contentSources: IContentSource[] }) =>
- typeof Array.isArray(body?.users) && typeof Array.isArray(body?.contentSources),
})
);
@@ -40,7 +35,6 @@ export function registerGroupsRoute({
return enterpriseSearchRequestHandler.createRequest({
path: '/ws/org/groups',
body: request.body,
- hasValidData: (body: { created_at: string }) => typeof body?.created_at === 'string',
})(context, request, response);
}
);
@@ -71,9 +65,6 @@ export function registerSearchGroupsRoute({
return enterpriseSearchRequestHandler.createRequest({
path: '/ws/org/groups/search',
body: request.body,
- hasValidData: (body: { results: IGroup[]; meta: IMeta }) =>
- typeof Array.isArray(body?.results) &&
- typeof body?.meta?.page?.total_results === 'number',
})(context, request, response);
}
);
@@ -92,8 +83,6 @@ export function registerGroupRoute({ router, enterpriseSearchRequestHandler }: I
async (context, request, response) => {
return enterpriseSearchRequestHandler.createRequest({
path: `/ws/org/groups/${request.params.id}`,
- hasValidData: (body: IGroup) =>
- typeof body?.createdAt === 'string' && typeof body?.usersCount === 'number',
})(context, request, response);
}
);
@@ -116,8 +105,6 @@ export function registerGroupRoute({ router, enterpriseSearchRequestHandler }: I
return enterpriseSearchRequestHandler.createRequest({
path: `/ws/org/groups/${request.params.id}`,
body: request.body,
- hasValidData: (body: IGroup) =>
- typeof body?.createdAt === 'string' && typeof body?.usersCount === 'number',
})(context, request, response);
}
);
@@ -134,7 +121,6 @@ export function registerGroupRoute({ router, enterpriseSearchRequestHandler }: I
async (context, request, response) => {
return enterpriseSearchRequestHandler.createRequest({
path: `/ws/org/groups/${request.params.id}`,
- hasValidData: (body: { deleted: boolean }) => body?.deleted === true,
})(context, request, response);
}
);
@@ -156,7 +142,6 @@ export function registerGroupUsersRoute({
async (context, request, response) => {
return enterpriseSearchRequestHandler.createRequest({
path: `/ws/org/groups/${request.params.id}/group_users`,
- hasValidData: (body: IUser[]) => typeof Array.isArray(body),
})(context, request, response);
}
);
@@ -182,8 +167,6 @@ export function registerShareGroupRoute({
return enterpriseSearchRequestHandler.createRequest({
path: `/ws/org/groups/${request.params.id}/share`,
body: request.body,
- hasValidData: (body: IGroup) =>
- typeof body?.createdAt === 'string' && typeof body?.usersCount === 'number',
})(context, request, response);
}
);
@@ -209,8 +192,6 @@ export function registerAssignGroupRoute({
return enterpriseSearchRequestHandler.createRequest({
path: `/ws/org/groups/${request.params.id}/assign`,
body: request.body,
- hasValidData: (body: IGroup) =>
- typeof body?.createdAt === 'string' && typeof body?.usersCount === 'number',
})(context, request, response);
}
);
@@ -238,8 +219,6 @@ export function registerBoostsGroupRoute({
return enterpriseSearchRequestHandler.createRequest({
path: `/ws/org/groups/${request.params.id}/update_source_boosts`,
body: request.body,
- hasValidData: (body: IGroup) =>
- typeof body?.createdAt === 'string' && typeof body?.usersCount === 'number',
})(context, request, response);
}
);
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/overview.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/overview.test.ts
index a387cab31c17a..9317b1ada85af 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/overview.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/overview.test.ts
@@ -14,7 +14,11 @@ describe('Overview route', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockRouter = new MockRouter({ method: 'get', payload: 'query' });
+ mockRouter = new MockRouter({
+ method: 'get',
+ path: '/api/workplace_search/overview',
+ payload: 'query',
+ });
registerOverviewRoute({
...mockDependencies,
diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json
index 0a858969c4f6a..5c7eb50117d9b 100644
--- a/x-pack/plugins/event_log/generated/mappings.json
+++ b/x-pack/plugins/event_log/generated/mappings.json
@@ -81,6 +81,10 @@
"instance_id": {
"type": "keyword",
"ignore_above": 1024
+ },
+ "action_group_id": {
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts
index 57fe90a8e876e..3dbb43b15350f 100644
--- a/x-pack/plugins/event_log/generated/schemas.ts
+++ b/x-pack/plugins/event_log/generated/schemas.ts
@@ -60,6 +60,7 @@ export const EventSchema = schema.maybe(
alerting: schema.maybe(
schema.object({
instance_id: ecsString(),
+ action_group_id: ecsString(),
})
),
saved_objects: schema.maybe(
diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js
index fd149d132031e..c9af2b0aa57fb 100644
--- a/x-pack/plugins/event_log/scripts/mappings.js
+++ b/x-pack/plugins/event_log/scripts/mappings.js
@@ -18,6 +18,10 @@ exports.EcsKibanaExtensionsMappings = {
type: 'keyword',
ignore_above: 1024,
},
+ action_group_id: {
+ type: 'keyword',
+ ignore_above: 1024,
+ },
},
},
// array of saved object references, for "linking" via search
@@ -63,6 +67,7 @@ exports.EcsEventLogProperties = [
'user.name',
'kibana.server_uuid',
'kibana.alerting.instance_id',
+ 'kibana.alerting.action_group_id',
'kibana.saved_objects.rel',
'kibana.saved_objects.namespace',
'kibana.saved_objects.id',
diff --git a/x-pack/plugins/features/README.md b/x-pack/plugins/features/README.md
new file mode 100644
index 0000000000000..0951b0c13c314
--- /dev/null
+++ b/x-pack/plugins/features/README.md
@@ -0,0 +1,3 @@
+# `features` plugin
+
+The `features` plugin enhance Kibana with a per-feature privilege system.
\ No newline at end of file
diff --git a/x-pack/plugins/global_search_providers/README.md b/x-pack/plugins/global_search_providers/README.md
new file mode 100644
index 0000000000000..da78f5adbfb09
--- /dev/null
+++ b/x-pack/plugins/global_search_providers/README.md
@@ -0,0 +1,15 @@
+# Kibana `GlobalSearchProviders` plugin
+
+The globalSearchProviders plugin provides Kibana default search providers for the `GlobalSearch` plugin.
+
+## Server-side providers
+
+### SavedObjects
+
+This provider returns results for all saved object types that are searchable.
+
+## Client-side providers
+
+### Applications
+
+This provider returns results for the applications that are currently registered on the client-side.
diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx
index ad61641ea1e36..646978dd68153 100644
--- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx
+++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx
@@ -14,6 +14,9 @@ import { DataTierAllocationType } from '../../../public/application/sections/edi
import { Phases as PolicyPhases } from '../../../common/types';
+import { KibanaContextProvider } from '../../../public/shared_imports';
+import { createBreadcrumbsMock } from '../../../public/application/services/breadcrumbs.mock';
+
type Phases = keyof PolicyPhases;
import { POLICY_NAME } from './constants';
@@ -48,7 +51,17 @@ const testBedConfig: TestBedConfig = {
},
};
-const initTestBed = registerTestBed(EditPolicy, testBedConfig);
+const breadcrumbService = createBreadcrumbsMock();
+
+const MyComponent = (props: any) => {
+ return (
+
+
+
+ );
+};
+
+const initTestBed = registerTestBed(MyComponent, testBedConfig);
type SetupReturn = ReturnType;
@@ -208,6 +221,11 @@ export const setup = async () => {
setFreeze,
setIndexPriority: setIndexPriority('cold'),
},
+ delete: {
+ enable: enable('delete'),
+ setMinAgeValue: setMinAgeValue('delete'),
+ setMinAgeUnits: setMinAgeUnits('delete'),
+ },
},
};
};
diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts
index 11fadf51f27f8..4ee67d1ed8a19 100644
--- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts
+++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts
@@ -367,7 +367,6 @@ describe(' ', () => {
expect(testBed.find('snapshotPolicyCombobox').prop('data-currentvalue')).toEqual([
{
label: DELETE_PHASE_POLICY.policy.phases.delete?.actions.wait_for_snapshot?.policy,
- value: DELETE_PHASE_POLICY.policy.phases.delete?.actions.wait_for_snapshot?.policy,
},
]);
});
@@ -412,7 +411,7 @@ describe(' ', () => {
test('wait for snapshot field should delete action if field is empty', async () => {
const { actions } = testBed;
- actions.setWaitForSnapshotPolicy('');
+ await actions.setWaitForSnapshotPolicy('');
await actions.savePolicy();
const expected = {
diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx
index 4a3fedfb264ac..43910583ceec9 100644
--- a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx
+++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx
@@ -20,27 +20,27 @@ import {
notificationServiceMock,
fatalErrorsServiceMock,
} from '../../../../../src/core/public/mocks';
+
import { usageCollectionPluginMock } from '../../../../../src/plugins/usage_collection/public/mocks';
+
import { CloudSetup } from '../../../cloud/public';
import { EditPolicy } from '../../public/application/sections/edit_policy/edit_policy';
+import {
+ EditPolicyContextProvider,
+ EditPolicyContextValue,
+} from '../../public/application/sections/edit_policy/edit_policy_context';
+
import { KibanaContextProvider } from '../../public/shared_imports';
+
import { init as initHttp } from '../../public/application/services/http';
import { init as initUiMetric } from '../../public/application/services/ui_metric';
import { init as initNotification } from '../../public/application/services/notification';
import { PolicyFromES } from '../../common/types';
-import {
- positiveNumberRequiredMessage,
- policyNameRequiredMessage,
- policyNameStartsWithUnderscoreErrorMessage,
- policyNameContainsCommaErrorMessage,
- policyNameContainsSpaceErrorMessage,
- policyNameMustBeDifferentErrorMessage,
- policyNameAlreadyUsedErrorMessage,
-} from '../../public/application/services/policies/policy_validation';
import { i18nTexts } from '../../public/application/sections/edit_policy/i18n_texts';
import { editPolicyHelpers } from './helpers';
+import { defaultPolicy } from '../../public/application/constants';
// @ts-ignore
initHttp(axios.create({ adapter: axiosXhrAdapter }));
@@ -122,14 +122,11 @@ const noRollover = async (rendered: ReactWrapper) => {
const getNodeAttributeSelect = (rendered: ReactWrapper, phase: string) => {
return findTestSubject(rendered, `${phase}-selectedNodeAttrs`);
};
-const setPolicyName = (rendered: ReactWrapper, policyName: string) => {
+const setPolicyName = async (rendered: ReactWrapper, policyName: string) => {
const policyNameField = findTestSubject(rendered, 'policyNameField');
- policyNameField.simulate('change', { target: { value: policyName } });
- rendered.update();
-};
-const setPhaseAfterLegacy = (rendered: ReactWrapper, phase: string, after: string | number) => {
- const afterInput = rendered.find(`input#${phase}-selectedMinimumAge`);
- afterInput.simulate('change', { target: { value: after } });
+ await act(async () => {
+ policyNameField.simulate('change', { target: { value: policyName } });
+ });
rendered.update();
};
const setPhaseAfter = async (rendered: ReactWrapper, phase: string, after: string | number) => {
@@ -157,6 +154,32 @@ const save = async (rendered: ReactWrapper) => {
});
rendered.update();
};
+
+const MyComponent = ({
+ isCloudEnabled,
+ isNewPolicy,
+ policy: _policy,
+ existingPolicies,
+ getUrlForApp,
+ policyName,
+}: EditPolicyContextValue & { isCloudEnabled: boolean }) => {
+ return (
+
+
+
+
+
+ );
+};
+
describe('edit policy', () => {
beforeAll(() => {
jest.useFakeTimers();
@@ -179,14 +202,14 @@ describe('edit policy', () => {
beforeEach(() => {
component = (
-
-
-
+
);
({ http } = editPolicyHelpers.setup());
@@ -198,62 +221,78 @@ describe('edit policy', () => {
test('should show error when trying to save empty form', async () => {
const rendered = mountWithIntl(component);
await save(rendered);
- expectedErrorMessages(rendered, [policyNameRequiredMessage]);
+ expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameRequiredMessage]);
});
test('should show error when trying to save policy name with space', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'my policy');
- await save(rendered);
- expectedErrorMessages(rendered, [policyNameContainsSpaceErrorMessage]);
+ await setPolicyName(rendered, 'my policy');
+ waitForFormLibValidation(rendered);
+ expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameContainsInvalidChars]);
});
test('should show error when trying to save policy name that is already used', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'testy0');
- rendered.update();
- await save(rendered);
- expectedErrorMessages(rendered, [policyNameAlreadyUsedErrorMessage]);
+ await setPolicyName(rendered, 'testy0');
+ waitForFormLibValidation(rendered);
+ expectedErrorMessages(rendered, [
+ i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage,
+ ]);
});
test('should show error when trying to save as new policy but using the same name', async () => {
component = (
-
);
const rendered = mountWithIntl(component);
findTestSubject(rendered, 'saveAsNewSwitch').simulate('click');
rendered.update();
- setPolicyName(rendered, 'testy0');
- await save(rendered);
- expectedErrorMessages(rendered, [policyNameMustBeDifferentErrorMessage]);
+ await setPolicyName(rendered, 'testy0');
+ waitForFormLibValidation(rendered);
+ expectedErrorMessages(rendered, [
+ i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage,
+ ]);
});
test('should show error when trying to save policy name with comma', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'my,policy');
- await save(rendered);
- expectedErrorMessages(rendered, [policyNameContainsCommaErrorMessage]);
+ await setPolicyName(rendered, 'my,policy');
+ waitForFormLibValidation(rendered);
+ expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameContainsInvalidChars]);
});
test('should show error when trying to save policy name starting with underscore', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, '_mypolicy');
- await save(rendered);
- expectedErrorMessages(rendered, [policyNameStartsWithUnderscoreErrorMessage]);
+ await setPolicyName(rendered, '_mypolicy');
+ waitForFormLibValidation(rendered);
+ expectedErrorMessages(rendered, [
+ i18nTexts.editPolicy.errors.policyNameStartsWithUnderscoreErrorMessage,
+ ]);
});
test('should show correct json in policy flyout', async () => {
- const rendered = mountWithIntl(component);
+ const rendered = mountWithIntl(
+
+ );
await act(async () => {
findTestSubject(rendered, 'requestButton').simulate('click');
});
rendered.update();
+
const json = rendered.find(`code`).text();
- const expected = `PUT _ilm/policy/\n${JSON.stringify(
+ const expected = `PUT _ilm/policy/my-policy\n${JSON.stringify(
{
policy: {
phases: {
@@ -282,7 +321,7 @@ describe('edit policy', () => {
test('should show errors when trying to save with no max size and no max age', async () => {
const rendered = mountWithIntl(component);
expect(findTestSubject(rendered, 'rolloverSettingsRequired').exists()).toBeFalsy();
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored');
await act(async () => {
maxSizeInput.simulate('change', { target: { value: '' } });
@@ -298,7 +337,7 @@ describe('edit policy', () => {
});
test('should show number above 0 required error when trying to save with -1 for max size', async () => {
const rendered = mountWithIntl(component);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored');
await act(async () => {
maxSizeInput.simulate('change', { target: { value: '-1' } });
@@ -309,7 +348,7 @@ describe('edit policy', () => {
});
test('should show number above 0 required error when trying to save with 0 for max size', async () => {
const rendered = mountWithIntl(component);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored');
await act(async () => {
maxSizeInput.simulate('change', { target: { value: '-1' } });
@@ -319,7 +358,7 @@ describe('edit policy', () => {
});
test('should show number above 0 required error when trying to save with -1 for max age', async () => {
const rendered = mountWithIntl(component);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge');
await act(async () => {
maxAgeInput.simulate('change', { target: { value: '-1' } });
@@ -329,7 +368,7 @@ describe('edit policy', () => {
});
test('should show number above 0 required error when trying to save with 0 for max age', async () => {
const rendered = mountWithIntl(component);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge');
await act(async () => {
maxAgeInput.simulate('change', { target: { value: '0' } });
@@ -337,21 +376,21 @@ describe('edit policy', () => {
waitForFormLibValidation(rendered);
expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]);
});
- test('should show forcemerge input when rollover enabled', () => {
+ test('should show forcemerge input when rollover enabled', async () => {
const rendered = mountWithIntl(component);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeTruthy();
});
test('should hide forcemerge input when rollover is disabled', async () => {
const rendered = mountWithIntl(component);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await noRollover(rendered);
waitForFormLibValidation(rendered);
expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeFalsy();
});
test('should show positive number required above zero error when trying to save hot phase with 0 for force merge', async () => {
const rendered = mountWithIntl(component);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
act(() => {
findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click');
});
@@ -365,7 +404,7 @@ describe('edit policy', () => {
});
test('should show positive number above 0 required error when trying to save hot phase with -1 for force merge', async () => {
const rendered = mountWithIntl(component);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click');
rendered.update();
const forcemergeInput = findTestSubject(rendered, 'hot-selectedForceMergeSegments');
@@ -379,7 +418,7 @@ describe('edit policy', () => {
test('should show positive number required error when trying to save with -1 for index priority', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await setPhaseIndexPriority(rendered, 'hot', '-1');
waitForFormLibValidation(rendered);
expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]);
@@ -397,7 +436,7 @@ describe('edit policy', () => {
test('should show number required error when trying to save empty warm phase', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
await setPhaseAfter(rendered, 'warm', '');
waitForFormLibValidation(rendered);
@@ -406,7 +445,7 @@ describe('edit policy', () => {
test('should allow 0 for phase timing', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
await setPhaseAfter(rendered, 'warm', '0');
waitForFormLibValidation(rendered);
@@ -415,7 +454,7 @@ describe('edit policy', () => {
test('should show positive number required error when trying to save warm phase with -1 for after', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
await setPhaseAfter(rendered, 'warm', '-1');
waitForFormLibValidation(rendered);
@@ -424,7 +463,7 @@ describe('edit policy', () => {
test('should show positive number required error when trying to save warm phase with -1 for index priority', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
await setPhaseAfter(rendered, 'warm', '1');
await setPhaseAfter(rendered, 'warm', '-1');
@@ -434,7 +473,7 @@ describe('edit policy', () => {
test('should show positive number required above zero error when trying to save warm phase with 0 for shrink', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
act(() => {
findTestSubject(rendered, 'shrinkSwitch').simulate('click');
@@ -451,7 +490,7 @@ describe('edit policy', () => {
test('should show positive number above 0 required error when trying to save warm phase with -1 for shrink', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
await setPhaseAfter(rendered, 'warm', '1');
act(() => {
@@ -468,7 +507,7 @@ describe('edit policy', () => {
test('should show positive number required above zero error when trying to save warm phase with 0 for force merge', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
await setPhaseAfter(rendered, 'warm', '1');
act(() => {
@@ -485,7 +524,7 @@ describe('edit policy', () => {
test('should show positive number above 0 required error when trying to save warm phase with -1 for force merge', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
await setPhaseAfter(rendered, 'warm', '1');
await act(async () => {
@@ -503,7 +542,7 @@ describe('edit policy', () => {
server.respondImmediately = false;
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy();
expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy();
@@ -517,7 +556,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
await openNodeAttributesSection(rendered, 'warm');
@@ -527,7 +566,7 @@ describe('edit policy', () => {
test('should show node attributes input when attributes exist', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
await openNodeAttributesSection(rendered, 'warm');
@@ -539,7 +578,7 @@ describe('edit policy', () => {
test('should show view node attributes link when attribute selected and show flyout when clicked', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
await openNodeAttributesSection(rendered, 'warm');
@@ -568,7 +607,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
expect(findTestSubject(rendered, 'defaultAllocationWarning').exists()).toBeTruthy();
@@ -581,7 +620,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeTruthy();
@@ -594,7 +633,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeFalsy();
@@ -611,7 +650,7 @@ describe('edit policy', () => {
test('should allow 0 for phase timing', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
await setPhaseAfter(rendered, 'cold', '0');
waitForFormLibValidation(rendered);
@@ -621,7 +660,7 @@ describe('edit policy', () => {
test('should show positive number required error when trying to save cold phase with -1 for after', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
await setPhaseAfter(rendered, 'cold', '-1');
waitForFormLibValidation(rendered);
@@ -631,7 +670,7 @@ describe('edit policy', () => {
server.respondImmediately = false;
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy();
expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy();
@@ -645,7 +684,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
await openNodeAttributesSection(rendered, 'cold');
@@ -655,7 +694,7 @@ describe('edit policy', () => {
test('should show node attributes input when attributes exist', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
await openNodeAttributesSection(rendered, 'cold');
@@ -667,7 +706,7 @@ describe('edit policy', () => {
test('should show view node attributes link when attribute selected and show flyout when clicked', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
await openNodeAttributesSection(rendered, 'cold');
@@ -689,7 +728,7 @@ describe('edit policy', () => {
test('should show positive number required error when trying to save with -1 for index priority', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
await setPhaseAfter(rendered, 'cold', '1');
await setPhaseIndexPriority(rendered, 'cold', '-1');
@@ -704,7 +743,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
expect(findTestSubject(rendered, 'defaultAllocationWarning').exists()).toBeTruthy();
@@ -717,7 +756,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeTruthy();
@@ -730,7 +769,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeFalsy();
@@ -740,20 +779,20 @@ describe('edit policy', () => {
test('should allow 0 for phase timing', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'delete');
- setPhaseAfterLegacy(rendered, 'delete', '0');
- await save(rendered);
+ await setPhaseAfter(rendered, 'delete', '0');
+ waitForFormLibValidation(rendered);
expectedErrorMessages(rendered, []);
});
test('should show positive number required error when trying to save delete phase with -1 for after', async () => {
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'delete');
- setPhaseAfterLegacy(rendered, 'delete', '-1');
- await save(rendered);
- expectedErrorMessages(rendered, [positiveNumberRequiredMessage]);
+ await setPhaseAfter(rendered, 'delete', '-1');
+ waitForFormLibValidation(rendered);
+ expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]);
});
});
describe('not on cloud', () => {
@@ -768,7 +807,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
@@ -782,14 +821,13 @@ describe('edit policy', () => {
describe('on cloud', () => {
beforeEach(() => {
component = (
-
-
-
+
);
({ http } = editPolicyHelpers.setup());
({ server, httpRequestsMockHelpers } = http);
@@ -808,7 +846,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
@@ -829,7 +867,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'warm');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
@@ -849,7 +887,7 @@ describe('edit policy', () => {
});
const rendered = mountWithIntl(component);
await noRollover(rendered);
- setPolicyName(rendered, 'mypolicy');
+ await setPolicyName(rendered, 'mypolicy');
await activatePhase(rendered, 'cold');
expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy();
expect(findTestSubject(rendered, 'cloudDataTierCallout').exists()).toBeTruthy();
diff --git a/x-pack/plugins/index_lifecycle_management/kibana.json b/x-pack/plugins/index_lifecycle_management/kibana.json
index 1b0a73c6a0133..21e7e7888acb9 100644
--- a/x-pack/plugins/index_lifecycle_management/kibana.json
+++ b/x-pack/plugins/index_lifecycle_management/kibana.json
@@ -6,7 +6,8 @@
"requiredPlugins": [
"licensing",
"management",
- "features"
+ "features",
+ "share"
],
"optionalPlugins": [
"cloud",
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx
index f7f8b30324bca..856981fe5c4f9 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx
@@ -13,6 +13,7 @@ import { UIM_APP_LOAD } from './constants/ui_metric';
import { EditPolicy } from './sections/edit_policy';
import { PolicyTable } from './sections/policy_table';
import { trackUiMetric } from './services/ui_metric';
+import { ROUTES } from './services/navigation';
export const App = ({
history,
@@ -28,14 +29,14 @@ export const App = ({
return (
-
+
}
/>
}
/>
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx
index 7a7fd20e96c63..3d4cc7dbbd1d4 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx
@@ -14,17 +14,20 @@ import { KibanaContextProvider } from '../shared_imports';
import { App } from './app';
+import { BreadcrumbService } from './services/breadcrumbs';
+
export const renderApp = (
element: Element,
I18nContext: I18nStart['Context'],
history: ScopedHistory,
navigateToApp: ApplicationStart['navigateToApp'],
getUrlForApp: ApplicationStart['getUrlForApp'],
+ breadcrumbService: BreadcrumbService,
cloud?: CloudSetup
): UnmountCallback => {
render(
-
+
,
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/lib/policies.ts b/x-pack/plugins/index_lifecycle_management/public/application/lib/policies.ts
new file mode 100644
index 0000000000000..c4a91978a3765
--- /dev/null
+++ b/x-pack/plugins/index_lifecycle_management/public/application/lib/policies.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { PolicyFromES } from '../../../common/types';
+
+export const splitSizeAndUnits = (field: string): { size: string; units: string } => {
+ let size = '';
+ let units = '';
+
+ const result = /(\d+)(\w+)/.exec(field);
+ if (result) {
+ size = result[1];
+ units = result[2];
+ }
+
+ return {
+ size,
+ units,
+ };
+};
+
+export const getPolicyByName = (
+ policies: PolicyFromES[] | null | undefined,
+ policyName: string = ''
+): PolicyFromES | undefined => {
+ if (policies && policies.length > 0) {
+ return policies.find((policy: PolicyFromES) => policy.name === policyName);
+ }
+};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts
index a04608338718e..326f6ff87dc3b 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts
@@ -7,11 +7,8 @@
export { ActiveBadge } from './active_badge';
export { ErrableFormRow } from './form_errors';
export { LearnMoreLink } from './learn_more_link';
-export { MinAgeInput } from './min_age_input_legacy';
export { OptionalLabel } from './optional_label';
-export { PhaseErrorMessage } from './phase_error_message';
export { PolicyJsonFlyout } from './policy_json_flyout';
-export { SnapshotPolicies } from './snapshot_policies';
export { DescribedFormField } from './described_form_field';
export * from './phases';
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx
deleted file mode 100644
index 6fcf35b799289..0000000000000
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx
+++ /dev/null
@@ -1,263 +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 React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
-
-import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui';
-
-import { LearnMoreLink } from './learn_more_link';
-import { ErrableFormRow } from './form_errors';
-import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation';
-import { PhaseWithMinAge, Phases } from '../../../../../common/types';
-
-function getTimingLabelForPhase(phase: keyof Phases) {
- // NOTE: Hot phase isn't necessary, because indices begin in the hot phase.
- switch (phase) {
- case 'warm':
- return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeLabel', {
- defaultMessage: 'Timing for warm phase',
- });
-
- case 'cold':
- return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeLabel', {
- defaultMessage: 'Timing for cold phase',
- });
-
- case 'delete':
- return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeLabel', {
- defaultMessage: 'Timing for delete phase',
- });
- }
-}
-
-function getUnitsAriaLabelForPhase(phase: keyof Phases) {
- // NOTE: Hot phase isn't necessary, because indices begin in the hot phase.
- switch (phase) {
- case 'warm':
- return i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeUnitsAriaLabel',
- {
- defaultMessage: 'Units for timing of warm phase',
- }
- );
-
- case 'cold':
- return i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeUnitsAriaLabel',
- {
- defaultMessage: 'Units for timing of cold phase',
- }
- );
-
- case 'delete':
- return i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeUnitsAriaLabel',
- {
- defaultMessage: 'Units for timing of delete phase',
- }
- );
- }
-}
-
-interface Props {
- rolloverEnabled: boolean;
- errors?: PhaseValidationErrors;
- phase: keyof Phases & string;
- phaseData: T;
- setPhaseData: (dataKey: keyof T & string, value: string) => void;
- isShowingErrors: boolean;
-}
-
-export const MinAgeInput = ({
- rolloverEnabled,
- errors,
- phaseData,
- phase,
- setPhaseData,
- isShowingErrors,
-}: React.PropsWithChildren>): React.ReactElement => {
- let daysOptionLabel;
- let hoursOptionLabel;
- let minutesOptionLabel;
- let secondsOptionLabel;
- let millisecondsOptionLabel;
- let microsecondsOptionLabel;
- let nanosecondsOptionLabel;
-
- if (rolloverEnabled) {
- daysOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel',
- {
- defaultMessage: 'days from rollover',
- }
- );
-
- hoursOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel',
- {
- defaultMessage: 'hours from rollover',
- }
- );
- minutesOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.rolloverMinutesOptionLabel',
- {
- defaultMessage: 'minutes from rollover',
- }
- );
-
- secondsOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.rolloverSecondsOptionLabel',
- {
- defaultMessage: 'seconds from rollover',
- }
- );
- millisecondsOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.rolloverMilliSecondsOptionLabel',
- {
- defaultMessage: 'milliseconds from rollover',
- }
- );
-
- microsecondsOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel',
- {
- defaultMessage: 'microseconds from rollover',
- }
- );
-
- nanosecondsOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.rolloverNanoSecondsOptionLabel',
- {
- defaultMessage: 'nanoseconds from rollover',
- }
- );
- } else {
- daysOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.creationDaysOptionLabel',
- {
- defaultMessage: 'days from index creation',
- }
- );
-
- hoursOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.creationHoursOptionLabel',
- {
- defaultMessage: 'hours from index creation',
- }
- );
-
- minutesOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.creationMinutesOptionLabel',
- {
- defaultMessage: 'minutes from index creation',
- }
- );
-
- secondsOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.creationSecondsOptionLabel',
- {
- defaultMessage: 'seconds from index creation',
- }
- );
-
- millisecondsOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.creationMilliSecondsOptionLabel',
- {
- defaultMessage: 'milliseconds from index creation',
- }
- );
-
- microsecondsOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.creationMicroSecondsOptionLabel',
- {
- defaultMessage: 'microseconds from index creation',
- }
- );
-
- nanosecondsOptionLabel = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.creationNanoSecondsOptionLabel',
- {
- defaultMessage: 'nanoseconds from index creation',
- }
- );
- }
-
- // check that these strings are valid properties
- const selectedMinimumAgeProperty = propertyof('selectedMinimumAge');
- const selectedMinimumAgeUnitsProperty = propertyof('selectedMinimumAgeUnits');
- return (
-
-
-
- }
- />
- }
- >
- {
- setPhaseData(selectedMinimumAgeProperty, e.target.value);
- }}
- min={0}
- />
-
-
-
-
- setPhaseData(selectedMinimumAgeUnitsProperty, e.target.value)}
- options={[
- {
- value: 'd',
- text: daysOptionLabel,
- },
- {
- value: 'h',
- text: hoursOptionLabel,
- },
- {
- value: 'm',
- text: minutesOptionLabel,
- },
- {
- value: 's',
- text: secondsOptionLabel,
- },
- {
- value: 'ms',
- text: millisecondsOptionLabel,
- },
- {
- value: 'micros',
- text: microsecondsOptionLabel,
- },
- {
- value: 'nanos',
- text: nanosecondsOptionLabel,
- },
- ]}
- />
-
-
-
- );
-};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx
deleted file mode 100644
index 750f68543f221..0000000000000
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import React from 'react';
-import { EuiBadge } from '@elastic/eui';
-import { FormattedMessage } from '@kbn/i18n/react';
-
-export const PhaseErrorMessage = ({ isShowingErrors }: { isShowingErrors: boolean }) => {
- return isShowingErrors ? (
-
-
-
- ) : null;
-};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx
index 84e955a91ad7c..b87243bd1a9a1 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx
@@ -13,19 +13,13 @@ import { EuiDescribedFormGroup, EuiTextColor } from '@elastic/eui';
import { Phases } from '../../../../../../../common/types';
-import {
- useFormData,
- useFormContext,
- UseField,
- ToggleField,
- NumericField,
-} from '../../../../../../shared_imports';
+import { useFormData, UseField, ToggleField, NumericField } from '../../../../../../shared_imports';
import { useEditPolicyContext } from '../../../edit_policy_context';
-import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, DescribedFormField } from '../../';
+import { LearnMoreLink, ActiveBadge, DescribedFormField } from '../../';
-import { MinAgeInputField, DataTierAllocationField, SetPriorityInput } from '../shared';
+import { MinAgeInputField, DataTierAllocationField, SetPriorityInput } from '../shared_fields';
const i18nTexts = {
dataTierAllocation: {
@@ -43,15 +37,13 @@ const formFieldPaths = {
};
export const ColdPhase: FunctionComponent = () => {
- const { originalPolicy } = useEditPolicyContext();
- const form = useFormContext();
+ const { policy } = useEditPolicyContext();
const [formData] = useFormData({
watch: [formFieldPaths.enabled],
});
const enabled = get(formData, formFieldPaths.enabled);
- const isShowingErrors = form.isValid === false;
return (
@@ -66,8 +58,7 @@ export const ColdPhase: FunctionComponent = () => {
defaultMessage="Cold phase"
/>
{' '}
- {enabled && !isShowingErrors ?
: null}
-
+ {enabled &&
}
}
titleSize="s"
@@ -128,9 +119,7 @@ export const ColdPhase: FunctionComponent = () => {
'xpack.indexLifecycleMgmt.editPolicy.coldPhase.numberOfReplicas.switchLabel',
{ defaultMessage: 'Set replicas' }
),
- initialValue: Boolean(
- originalPolicy.phases.cold?.actions?.allocate?.number_of_replicas
- ),
+ initialValue: Boolean(policy.phases.cold?.actions?.allocate?.number_of_replicas),
}}
fullWidth
>
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx
similarity index 50%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx
index 78ae66327654c..37323b97edc92 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx
@@ -7,53 +7,24 @@
import React, { FunctionComponent, Fragment } from 'react';
import { get } from 'lodash';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiDescribedFormGroup, EuiSwitch, EuiTextColor, EuiFormRow } from '@elastic/eui';
+import { EuiDescribedFormGroup, EuiTextColor, EuiFormRow } from '@elastic/eui';
-import { DeletePhase as DeletePhaseInterface, Phases } from '../../../../../../common/types';
+import { useFormData, UseField, ToggleField } from '../../../../../../shared_imports';
-import { useFormData } from '../../../../../shared_imports';
+import { ActiveBadge, LearnMoreLink, OptionalLabel } from '../../index';
-import { PhaseValidationErrors } from '../../../../services/policies/policy_validation';
+import { MinAgeInputField, SnapshotPoliciesField } from '../shared_fields';
-import {
- ActiveBadge,
- LearnMoreLink,
- OptionalLabel,
- PhaseErrorMessage,
- MinAgeInput,
- SnapshotPolicies,
-} from '../';
-import { useRolloverPath } from './shared';
-
-const deleteProperty: keyof Phases = 'delete';
-const phaseProperty = (propertyName: keyof DeletePhaseInterface) => propertyName;
-
-interface Props {
- setPhaseData: (key: keyof DeletePhaseInterface & string, value: string | boolean) => void;
- phaseData: DeletePhaseInterface;
- isShowingErrors: boolean;
- errors?: PhaseValidationErrors;
- getUrlForApp: (
- appId: string,
- options?: {
- path?: string;
- absolute?: boolean;
- }
- ) => string;
-}
+const formFieldPaths = {
+ enabled: '_meta.delete.enabled',
+};
-export const DeletePhase: FunctionComponent = ({
- setPhaseData,
- phaseData,
- errors,
- isShowingErrors,
- getUrlForApp,
-}) => {
+export const DeletePhase: FunctionComponent = () => {
const [formData] = useFormData({
- watch: useRolloverPath,
+ watch: formFieldPaths.enabled,
});
- const hotPhaseRolloverEnabled = get(formData, useRolloverPath);
+ const enabled = get(formData, formFieldPaths.enabled);
return (
@@ -66,8 +37,7 @@ export const DeletePhase: FunctionComponent
= ({
defaultMessage="Delete phase"
/>
{' '}
- {phaseData.phaseEnabled && !isShowingErrors ? : null}
-
+ {enabled && }
}
titleSize="s"
@@ -79,39 +49,23 @@ export const DeletePhase: FunctionComponent = ({
defaultMessage="You no longer need your index. You can define when it is safe to delete it."
/>
-
- }
- id={`${deleteProperty}-${phaseProperty('phaseEnabled')}`}
- checked={phaseData.phaseEnabled}
- onChange={(e) => {
- setPhaseData(phaseProperty('phaseEnabled'), e.target.checked);
+
}
fullWidth
>
- {phaseData.phaseEnabled ? (
-
- errors={errors}
- phaseData={phaseData}
- phase={deleteProperty}
- isShowingErrors={isShowingErrors}
- setPhaseData={setPhaseData}
- rolloverEnabled={hotPhaseRolloverEnabled}
- />
- ) : (
-
- )}
+ {enabled && }
- {phaseData.phaseEnabled ? (
+ {enabled ? (
@@ -145,11 +99,7 @@ export const DeletePhase: FunctionComponent = ({
}
>
- setPhaseData(phaseProperty('waitForSnapshotPolicy'), value)}
- getUrlForApp={getUrlForApp}
- />
+
) : null}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/constants.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/index.ts
similarity index 82%
rename from x-pack/plugins/maps/public/classes/sources/es_search_source/constants.js
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/index.ts
index d7d11440c360b..488e4e26cfce0 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_search_source/constants.js
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const DEFAULT_FILTER_BY_MAP_BOUNDS = true;
+export { DeletePhase } from './delete_phase';
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx
index a184ddf5148b9..629c1388f61fb 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx
@@ -19,7 +19,6 @@ import {
import { Phases } from '../../../../../../../common/types';
import {
- useFormContext,
useFormData,
UseField,
SelectField,
@@ -29,26 +28,24 @@ import {
import { i18nTexts } from '../../../i18n_texts';
-import { ROLLOVER_EMPTY_VALIDATION } from '../../../form_validations';
+import { ROLLOVER_EMPTY_VALIDATION } from '../../../form';
import { ROLLOVER_FORM_PATHS } from '../../../constants';
-import { LearnMoreLink, ActiveBadge, PhaseErrorMessage } from '../../';
+import { LearnMoreLink, ActiveBadge } from '../../';
-import { Forcemerge, SetPriorityInput, useRolloverPath } from '../shared';
+import { Forcemerge, SetPriorityInput, useRolloverPath } from '../shared_fields';
import { maxSizeStoredUnits, maxAgeUnits } from './constants';
const hotProperty: keyof Phases = 'hot';
export const HotPhase: FunctionComponent = () => {
- const form = useFormContext();
const [formData] = useFormData({
watch: useRolloverPath,
});
const isRolloverEnabled = get(formData, useRolloverPath);
- const isShowingErrors = form.isValid === false;
const [showEmptyRolloverFieldsError, setShowEmptyRolloverFieldsError] = useState(false);
return (
@@ -62,8 +59,7 @@ export const HotPhase: FunctionComponent = () => {
defaultMessage="Hot phase"
/>
{' '}
- {isShowingErrors ? null : }
-
+
}
titleSize="s"
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/cloud_data_tier_callout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/cloud_data_tier_callout.tsx
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/cloud_data_tier_callout.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/cloud_data_tier_callout.tsx
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.scss b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.scss
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.scss
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.scss
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_notice.tsx
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_notice.tsx
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/index.ts
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/index.ts
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/index.ts
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/no_node_attributes_warning.tsx
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/no_node_attributes_warning.tsx
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx
similarity index 90%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx
index 407bb9ea92e85..c1676d7074dbc 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx
@@ -10,12 +10,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiButtonEmpty, EuiText, EuiSpacer } from '@elastic/eui';
-import { PhaseWithAllocationAction } from '../../../../../../../../../common/types';
-
import { UseField, SelectField, useFormData } from '../../../../../../../../shared_imports';
-import { propertyof } from '../../../../../../../services/policies/policy_validation';
-
import { LearnMoreLink } from '../../../../learn_more_link';
import { NodeAttrsDetails } from './node_attrs_details';
@@ -61,9 +57,6 @@ export const NodeAllocation: FunctionComponent = ({ phase, nodes })
nodeOptions.sort((a, b) => a.value.localeCompare(b.value));
- // check that this string is a valid property
- const nodeAttrsProperty = propertyof('selectedNodeAttrs');
-
return (
<>
@@ -100,7 +93,7 @@ export const NodeAllocation: FunctionComponent = ({ phase, nodes })
) : undefined,
euiFieldProps: {
- 'data-test-subj': `${phase}-${nodeAttrsProperty}`,
+ 'data-test-subj': `${phase}-selectedNodeAttrs`,
options: [{ text: i18nTexts.doNotModifyAllocationOption, value: '' }].concat(
nodeOptions
),
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_attrs_details.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_attrs_details.tsx
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_attrs_details.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_attrs_details.tsx
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_data_provider.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_data_provider.tsx
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_data_provider.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_data_provider.tsx
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/types.ts
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/types.ts
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/types.ts
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/index.ts
similarity index 100%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/index.ts
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/index.ts
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx
similarity index 94%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx
index b410bd0e6b3b0..b05d49be497cd 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx
@@ -21,11 +21,11 @@ interface Props {
}
export const Forcemerge: React.FunctionComponent = ({ phase }) => {
- const { originalPolicy } = useEditPolicyContext();
+ const { policy } = useEditPolicyContext();
const initialToggleValue = useMemo(() => {
- return Boolean(originalPolicy.phases[phase]?.actions?.forcemerge);
- }, [originalPolicy, phase]);
+ return Boolean(policy.phases[phase]?.actions?.forcemerge);
+ }, [policy, phase]);
return (
= ({ phase }) => {
- const phaseIndexPriorityProperty = propertyof('phaseIndexPriority');
return (
= ({ phase }) => {
componentProps={{
fullWidth: false,
euiFieldProps: {
- 'data-test-subj': `${phase}-${phaseIndexPriorityProperty}`,
- min: 1,
+ 'data-test-subj': `${phase}-phaseIndexPriority`,
+ min: 0,
},
}}
/>
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx
similarity index 68%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies.tsx
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx
index cc2849b5c8e9c..e9f9f331e410a 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx
@@ -4,52 +4,39 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Fragment } from 'react';
-
+import React from 'react';
+import { get } from 'lodash';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
-import { ApplicationStart } from 'kibana/public';
import {
EuiButtonIcon,
EuiCallOut,
- EuiComboBox,
EuiComboBoxOptionOption,
EuiLink,
EuiSpacer,
} from '@elastic/eui';
-import { useLoadSnapshotPolicies } from '../../../services/api';
+import { UseField, ComboBoxField, useFormData } from '../../../../../../shared_imports';
+import { useLoadSnapshotPolicies } from '../../../../../services/api';
+import { useEditPolicyContext } from '../../../edit_policy_context';
+
+const waitForSnapshotFormField = 'phases.delete.actions.wait_for_snapshot.policy';
-interface Props {
- value: string;
- onChange: (value: string) => void;
- getUrlForApp: ApplicationStart['getUrlForApp'];
-}
-export const SnapshotPolicies: React.FunctionComponent = ({
- value,
- onChange,
- getUrlForApp,
-}) => {
+export const SnapshotPoliciesField: React.FunctionComponent = () => {
+ const { getUrlForApp } = useEditPolicyContext();
const { error, isLoading, data, resendRequest } = useLoadSnapshotPolicies();
+ const [formData] = useFormData({
+ watch: waitForSnapshotFormField,
+ });
+
+ const selectedSnapshotPolicy = get(formData, waitForSnapshotFormField);
const policies = data.map((name: string) => ({
label: name,
value: name,
}));
- const onComboChange = (options: EuiComboBoxOptionOption[]) => {
- if (options.length > 0) {
- onChange(options[0].label);
- } else {
- onChange('');
- }
- };
-
- const onCreateOption = (newValue: string) => {
- onChange(newValue);
- };
-
const getUrlForSnapshotPolicyWizard = () => {
return getUrlForApp('management', {
path: `data/snapshot_restore/add_policy`,
@@ -59,14 +46,14 @@ export const SnapshotPolicies: React.FunctionComponent = ({
let calloutContent;
if (error) {
calloutContent = (
-
+ <>
+ <>
= ({
}
)}
/>
-
+ >
}
>
= ({
defaultMessage="Refresh this field and enter the name of an existing snapshot policy."
/>
-
+ >
);
} else if (data.length === 0) {
calloutContent = (
-
+ <>
= ({
}}
/>
-
+ >
);
- } else if (value && !data.includes(value)) {
+ } else if (selectedSnapshotPolicy && !data.includes(selectedSnapshotPolicy)) {
calloutContent = (
-
+ <>
= ({
}}
/>
-
+ >
);
}
return (
-
-
+ path={waitForSnapshotFormField}>
+ {(field) => {
+ const singleSelectionArray: [selectedSnapshot?: string] = field.value
+ ? [field.value]
+ : [];
+
+ return (
+ {
+ field.setValue(newOption);
},
- ]
- : []
- }
- onChange={onComboChange}
- noSuggestions={!!(error || data.length === 0)}
- />
+ onChange: (options: EuiComboBoxOptionOption[]) => {
+ if (options.length > 0) {
+ field.setValue(options[0].label);
+ } else {
+ field.setValue('');
+ }
+ },
+ }}
+ />
+ );
+ }}
+
{calloutContent}
-
+ >
);
};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx
index 06c16e8bdd5ab..94fd2ee9edaca 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx
@@ -17,23 +17,17 @@ import {
EuiDescribedFormGroup,
} from '@elastic/eui';
-import {
- useFormData,
- UseField,
- ToggleField,
- useFormContext,
- NumericField,
-} from '../../../../../../shared_imports';
+import { useFormData, UseField, ToggleField, NumericField } from '../../../../../../shared_imports';
import { Phases } from '../../../../../../../common/types';
-import { useRolloverPath, MinAgeInputField, Forcemerge, SetPriorityInput } from '../shared';
+import { useRolloverPath, MinAgeInputField, Forcemerge, SetPriorityInput } from '../shared_fields';
import { useEditPolicyContext } from '../../../edit_policy_context';
-import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, DescribedFormField } from '../../';
+import { LearnMoreLink, ActiveBadge, DescribedFormField } from '../../';
-import { DataTierAllocationField } from '../shared';
+import { DataTierAllocationField } from '../shared_fields';
const i18nTexts = {
shrinkLabel: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.shrinkIndexLabel', {
@@ -54,8 +48,7 @@ const formFieldPaths = {
};
export const WarmPhase: FunctionComponent = () => {
- const { originalPolicy } = useEditPolicyContext();
- const form = useFormContext();
+ const { policy } = useEditPolicyContext();
const [formData] = useFormData({
watch: [useRolloverPath, formFieldPaths.enabled, formFieldPaths.warmPhaseOnRollover],
});
@@ -63,7 +56,6 @@ export const WarmPhase: FunctionComponent = () => {
const enabled = get(formData, formFieldPaths.enabled);
const hotPhaseRolloverEnabled = get(formData, useRolloverPath);
const warmPhaseOnRollover = get(formData, formFieldPaths.warmPhaseOnRollover);
- const isShowingErrors = form.isValid === false;
return (
@@ -77,8 +69,7 @@ export const WarmPhase: FunctionComponent = () => {
defaultMessage="Warm phase"
/>
{' '}
- {enabled && !isShowingErrors ?
: null}
-
+ {enabled &&
}
}
titleSize="s"
@@ -161,9 +152,7 @@ export const WarmPhase: FunctionComponent = () => {
'xpack.indexLifecycleMgmt.editPolicy.warmPhase.numberOfReplicas.switchLabel',
{ defaultMessage: 'Set replicas' }
),
- initialValue: Boolean(
- originalPolicy.phases.warm?.actions?.allocate?.number_of_replicas
- ),
+ initialValue: Boolean(policy.phases.warm?.actions?.allocate?.number_of_replicas),
}}
fullWidth
>
@@ -203,7 +192,7 @@ export const WarmPhase: FunctionComponent = () => {
'data-test-subj': 'shrinkSwitch',
label: i18nTexts.shrinkLabel,
'aria-label': i18nTexts.shrinkLabel,
- initialValue: Boolean(originalPolicy.phases.warm?.actions?.shrink),
+ initialValue: Boolean(policy.phases.warm?.actions?.shrink),
}}
fullWidth
>
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx
index 7098b018d6dfd..a8b1680ebde07 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx
@@ -7,7 +7,6 @@
import React, { useCallback, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-
import {
EuiButtonEmpty,
EuiCodeBlock,
@@ -25,19 +24,15 @@ import {
import { SerializedPolicy } from '../../../../../common/types';
import { useFormContext, useFormData } from '../../../../shared_imports';
+
import { FormInternal } from '../types';
interface Props {
- legacyPolicy: SerializedPolicy;
close: () => void;
policyName: string;
}
-export const PolicyJsonFlyout: React.FunctionComponent = ({
- policyName,
- close,
- legacyPolicy,
-}) => {
+export const PolicyJsonFlyout: React.FunctionComponent = ({ policyName, close }) => {
/**
* policy === undefined: we are checking validity
* policy === null: we have determined the policy is invalid
@@ -51,20 +46,11 @@ export const PolicyJsonFlyout: React.FunctionComponent = ({
const updatePolicy = useCallback(async () => {
setPolicy(undefined);
if (await validateForm()) {
- const p = getFormData() as SerializedPolicy;
- setPolicy({
- ...legacyPolicy,
- phases: {
- ...legacyPolicy.phases,
- hot: p.phases.hot,
- warm: p.phases.warm,
- cold: p.phases.cold,
- },
- });
+ setPolicy(getFormData() as SerializedPolicy);
} else {
setPolicy(null);
}
- }, [setPolicy, getFormData, legacyPolicy, validateForm]);
+ }, [setPolicy, getFormData, validateForm]);
useEffect(() => {
updatePolicy();
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx
index dfc3e7194da06..ebef80871b83d 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx
@@ -4,13 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useEffect } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
+
+import { useKibana } from '../../../shared_imports';
+
import { useLoadPoliciesList } from '../../services/api';
+import { getPolicyByName } from '../../lib/policies';
+import { defaultPolicy } from '../../constants';
import { EditPolicy as PresentationComponent } from './edit_policy';
+import { EditPolicyContextProvider } from './edit_policy_context';
interface RouterProps {
policyName: string;
@@ -33,7 +39,15 @@ export const EditPolicy: React.FunctionComponent {
+ const {
+ services: { breadcrumbService },
+ } = useKibana();
const { error, isLoading, data: policies, resendRequest } = useLoadPoliciesList(false);
+
+ useEffect(() => {
+ breadcrumbService.setBreadcrumbs('editPolicy');
+ }, [breadcrumbService]);
+
if (isLoading) {
return (
+
+
+
);
};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx
index 5397f5da2d6bb..1abbe884c2dc2 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx
@@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Fragment, useEffect, useState, useCallback, useMemo } from 'react';
+import React, { Fragment, useEffect, useState, useMemo } from 'react';
+import { get } from 'lodash';
import { RouteComponentProps } from 'react-router-dom';
@@ -16,7 +17,6 @@ import {
EuiButton,
EuiButtonEmpty,
EuiDescribedFormGroup,
- EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
@@ -30,31 +30,13 @@ import {
EuiTitle,
} from '@elastic/eui';
-import { useForm, Form } from '../../../shared_imports';
+import { useForm, Form, UseField, TextField, useFormData } from '../../../shared_imports';
import { toasts } from '../../services/notification';
-import { LegacyPolicy, PolicyFromES, SerializedPolicy } from '../../../../common/types';
-
-import { defaultPolicy } from '../../constants';
-
-import {
- validatePolicy,
- ValidationErrors,
- findFirstError,
-} from '../../services/policies/policy_validation';
-
-import { savePolicy } from '../../services/policies/policy_save';
+import { savePolicy } from './save_policy';
import {
- deserializePolicy,
- getPolicyByName,
- initializeNewPolicy,
- legacySerializePolicy,
-} from '../../services/policies/policy_serialization';
-
-import {
- ErrableFormRow,
LearnMoreLink,
PolicyJsonFlyout,
ColdPhase,
@@ -63,93 +45,66 @@ import {
WarmPhase,
} from './components';
-import { schema } from './form_schema';
-import { deserializer } from './deserializer';
-import { createSerializer } from './serializer';
+import { schema, deserializer, createSerializer, createPolicyNameValidations } from './form';
-import { EditPolicyContextProvider } from './edit_policy_context';
+import { useEditPolicyContext } from './edit_policy_context';
+import { FormInternal } from './types';
export interface Props {
- policies: PolicyFromES[];
- policyName: string;
- getUrlForApp: (
- appId: string,
- options?: {
- path?: string;
- absolute?: boolean;
- }
- ) => string;
history: RouteComponentProps['history'];
}
-const mergeAllSerializedPolicies = (
- serializedPolicy: SerializedPolicy,
- legacySerializedPolicy: SerializedPolicy
-): SerializedPolicy => {
- return {
- ...legacySerializedPolicy,
- phases: {
- ...legacySerializedPolicy.phases,
- hot: serializedPolicy.phases.hot,
- warm: serializedPolicy.phases.warm,
- cold: serializedPolicy.phases.cold,
- },
- };
-};
+const policyNamePath = 'name';
-export const EditPolicy: React.FunctionComponent = ({
- policies,
- policyName,
- history,
- getUrlForApp,
-}) => {
+export const EditPolicy: React.FunctionComponent = ({ history }) => {
useEffect(() => {
window.scrollTo(0, 0);
}, []);
- const [isShowingErrors, setIsShowingErrors] = useState(false);
- const [errors, setErrors] = useState();
const [isShowingPolicyJsonFlyout, setIsShowingPolicyJsonFlyout] = useState(false);
-
- const existingPolicy = getPolicyByName(policies, policyName);
+ const {
+ isNewPolicy,
+ policy: currentPolicy,
+ existingPolicies,
+ policyName,
+ } = useEditPolicyContext();
const serializer = useMemo(() => {
- return createSerializer(existingPolicy?.policy);
- }, [existingPolicy?.policy]);
+ return createSerializer(isNewPolicy ? undefined : currentPolicy);
+ }, [isNewPolicy, currentPolicy]);
- const originalPolicy = existingPolicy?.policy ?? defaultPolicy;
+ const [saveAsNew, setSaveAsNew] = useState(isNewPolicy);
+ const originalPolicyName: string = isNewPolicy ? '' : policyName!;
const { form } = useForm({
schema,
- defaultValue: originalPolicy,
+ defaultValue: {
+ ...currentPolicy,
+ name: originalPolicyName,
+ },
deserializer,
serializer,
});
- const [policy, setPolicy] = useState(() =>
- existingPolicy ? deserializePolicy(existingPolicy) : initializeNewPolicy(policyName)
+ const [formData] = useFormData({ form, watch: policyNamePath });
+ const currentPolicyName = get(formData, policyNamePath);
+
+ const policyNameValidations = useMemo(
+ () =>
+ createPolicyNameValidations({
+ originalPolicyName,
+ policies: existingPolicies,
+ saveAsNewPolicy: saveAsNew,
+ }),
+ [originalPolicyName, existingPolicies, saveAsNew]
);
- const isNewPolicy: boolean = !Boolean(existingPolicy);
- const [saveAsNew, setSaveAsNew] = useState(isNewPolicy);
- const originalPolicyName: string = existingPolicy ? existingPolicy.name : '';
-
const backToPolicyList = () => {
history.push('/policies');
};
const submit = async () => {
- setIsShowingErrors(true);
- const { data: formLibPolicy, isValid: newIsValid } = await form.submit();
- const [legacyIsValid, validationErrors] = validatePolicy(
- saveAsNew,
- policy,
- policies,
- originalPolicyName
- );
- setErrors(validationErrors);
-
- const isValid = legacyIsValid && newIsValid;
+ const { data: policy, isValid } = await form.submit();
if (!isValid) {
toasts.addDanger(
@@ -157,22 +112,11 @@ export const EditPolicy: React.FunctionComponent = ({
defaultMessage: 'Please fix the errors on this page.',
})
);
- // This functionality will not be required for once form lib is fully adopted for this form
- // because errors are reported as fields are edited.
- if (!legacyIsValid) {
- const firstError = findFirstError(validationErrors);
- const errorRowId = `${firstError ? firstError.replace('.', '-') : ''}-row`;
- const element = document.getElementById(errorRowId);
- if (element) {
- element.scrollIntoView({ block: 'center', inline: 'nearest' });
- }
- }
} else {
- const readSerializedPolicy = () => {
- const legacySerializedPolicy = legacySerializePolicy(policy, existingPolicy?.policy);
- return mergeAllSerializedPolicies(formLibPolicy, legacySerializedPolicy);
- };
- const success = await savePolicy(readSerializedPolicy, isNewPolicy || saveAsNew);
+ const success = await savePolicy(
+ { ...policy, name: saveAsNew ? currentPolicyName : originalPolicyName },
+ isNewPolicy || saveAsNew
+ );
if (success) {
backToPolicyList();
}
@@ -183,248 +127,217 @@ export const EditPolicy: React.FunctionComponent = ({
setIsShowingPolicyJsonFlyout(!isShowingPolicyJsonFlyout);
};
- const setPhaseData = useCallback(
- (phase: keyof LegacyPolicy['phases'], key: string, value: any) => {
- setPolicy((nextPolicy) => ({
- ...nextPolicy,
- phases: {
- ...nextPolicy.phases,
- [phase]: { ...nextPolicy.phases[phase], [key]: value },
- },
- }));
- },
- [setPolicy]
- );
-
- const setDeletePhaseData = useCallback(
- (key: string, value: any) => setPhaseData('delete', key, value),
- [setPhaseData]
- );
-
return (
-
-
-
-
-
-
- {isNewPolicy
- ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', {
- defaultMessage: 'Create an index lifecycle policy',
- })
- : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', {
- defaultMessage: 'Edit index lifecycle policy {originalPolicyName}',
- values: { originalPolicyName },
- })}
-
-
-
-
+ }
+ titleSize="s"
+ fullWidth
+ >
+
+ path={policyNamePath}
+ config={{
+ label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.policyNameLabel', {
+ defaultMessage: 'Policy name',
+ }),
+ helpText: i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.validPolicyNameMessage',
+ {
+ defaultMessage:
+ 'A policy name cannot start with an underscore and cannot contain a question mark or a space.',
+ }
+ ),
+ validations: policyNameValidations,
+ }}
+ component={TextField}
+ componentProps={{
+ fullWidth: false,
+ euiFieldProps: {
+ 'data-test-subj': 'policyNameField',
+ },
+ }}
+ />
+
+ ) : null}
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- 0
- }
- getUrlForApp={getUrlForApp}
- setPhaseData={setDeletePhaseData}
- phaseData={policy.phases.delete}
- />
+
-
-
-
-
-
-
-
- {saveAsNew ? (
-
- ) : (
-
- )}
-
-
-
-
-
+
+
+
+
+
+
+
+ {saveAsNew ? (
-
-
-
-
-
-
-
- {isShowingPolicyJsonFlyout ? (
-
- ) : (
+ ) : (
+
+ )}
+
+
+
+
+
- )}
-
-
-
-
- {isShowingPolicyJsonFlyout ? (
- setIsShowingPolicyJsonFlyout(false)}
- />
- ) : null}
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {isShowingPolicyJsonFlyout ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {isShowingPolicyJsonFlyout ? (
+ setIsShowingPolicyJsonFlyout(false)}
+ />
+ ) : null}
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx
index 4748c26d6cec1..da5f940b1b6c8 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx
@@ -5,10 +5,16 @@
*/
import React, { createContext, ReactChild, useContext } from 'react';
-import { SerializedPolicy } from '../../../../common/types';
+import { ApplicationStart } from 'kibana/public';
-interface EditPolicyContextValue {
- originalPolicy: SerializedPolicy;
+import { PolicyFromES, SerializedPolicy } from '../../../../common/types';
+
+export interface EditPolicyContextValue {
+ isNewPolicy: boolean;
+ policy: SerializedPolicy;
+ existingPolicies: PolicyFromES[];
+ getUrlForApp: ApplicationStart['getUrlForApp'];
+ policyName?: string;
}
const EditPolicyContext = createContext(null as any);
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts
similarity index 82%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts
index f0294a5391d21..5af8807f2dec8 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts
@@ -6,17 +6,17 @@
import { produce } from 'immer';
-import { SerializedPolicy } from '../../../../common/types';
+import { SerializedPolicy } from '../../../../../common/types';
-import { splitSizeAndUnits } from '../../services/policies/policy_serialization';
+import { splitSizeAndUnits } from '../../../lib/policies';
-import { determineDataTierAllocationType } from '../../lib';
+import { determineDataTierAllocationType } from '../../../lib';
-import { FormInternal } from './types';
+import { FormInternal } from '../types';
export const deserializer = (policy: SerializedPolicy): FormInternal => {
const {
- phases: { hot, warm, cold },
+ phases: { hot, warm, cold, delete: deletePhase },
} = policy;
const _meta: FormInternal['_meta'] = {
@@ -37,6 +37,9 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => {
dataTierAllocationType: determineDataTierAllocationType(cold?.actions),
freezeEnabled: Boolean(cold?.actions?.freeze),
},
+ delete: {
+ enabled: Boolean(deletePhase),
+ },
};
return produce(
@@ -86,6 +89,14 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => {
draft._meta.cold.minAgeUnit = minAge.units;
}
}
+
+ if (draft.phases.delete) {
+ if (draft.phases.delete.min_age) {
+ const minAge = splitSizeAndUnits(draft.phases.delete.min_age);
+ draft.phases.delete.min_age = minAge.size;
+ draft._meta.delete.minAgeUnit = minAge.units;
+ }
+ }
}
);
};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts
new file mode 100644
index 0000000000000..82fa478832582
--- /dev/null
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { deserializer } from './deserializer';
+
+export { createSerializer } from './serializer';
+
+export { schema } from './schema';
+
+export * from './validations';
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts
similarity index 90%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts
index 070f03f74b954..4d20db4018740 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts
@@ -6,18 +6,19 @@
import { i18n } from '@kbn/i18n';
-import { FormSchema, fieldValidators } from '../../../shared_imports';
-import { defaultSetPriority, defaultPhaseIndexPriority } from '../../constants';
+import { FormSchema, fieldValidators } from '../../../../shared_imports';
+import { defaultSetPriority, defaultPhaseIndexPriority } from '../../../constants';
-import { FormInternal } from './types';
+import { FormInternal } from '../types';
import {
ifExistsNumberGreaterThanZero,
ifExistsNumberNonNegative,
rolloverThresholdsValidator,
-} from './form_validations';
+ minAgeValidator,
+} from './validations';
-import { i18nTexts } from './i18n_texts';
+import { i18nTexts } from '../i18n_texts';
const { emptyField, numberGreaterThanField } = fieldValidators;
@@ -97,6 +98,18 @@ export const schema: FormSchema = {
label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel,
},
},
+ delete: {
+ enabled: {
+ defaultValue: false,
+ label: i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.deletePhase.activateWarmPhaseSwitchLabel',
+ { defaultMessage: 'Activate delete phase' }
+ ),
+ },
+ minAgeUnit: {
+ defaultValue: 'd',
+ },
+ },
},
phases: {
hot: {
@@ -177,15 +190,7 @@ export const schema: FormSchema = {
defaultValue: '0',
validations: [
{
- validator: (arg) =>
- numberGreaterThanField({
- than: 0,
- allowEquality: true,
- message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired,
- })({
- ...arg,
- value: arg.value === '' ? -Infinity : parseInt(arg.value, 10),
- }),
+ validator: minAgeValidator,
},
],
},
@@ -256,15 +261,7 @@ export const schema: FormSchema = {
defaultValue: '0',
validations: [
{
- validator: (arg) =>
- numberGreaterThanField({
- than: 0,
- allowEquality: true,
- message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired,
- })({
- ...arg,
- value: arg.value === '' ? -Infinity : parseInt(arg.value, 10),
- }),
+ validator: minAgeValidator,
},
],
},
@@ -292,5 +289,15 @@ export const schema: FormSchema = {
},
},
},
+ delete: {
+ min_age: {
+ defaultValue: '0',
+ validations: [
+ {
+ validator: minAgeValidator,
+ },
+ ],
+ },
+ },
},
};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer.ts
similarity index 90%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer.ts
index 564b5a2c4e397..2274efda426ad 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer.ts
@@ -4,12 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { isEmpty } from 'lodash';
+import { isEmpty, isNumber } from 'lodash';
-import { SerializedPolicy, SerializedActionWithAllocation } from '../../../../common/types';
+import { SerializedPolicy, SerializedActionWithAllocation } from '../../../../../common/types';
-import { FormInternal, DataAllocationMetaFields } from './types';
-import { isNumber } from '../../services/policies/policy_serialization';
+import { FormInternal, DataAllocationMetaFields } from '../types';
const serializeAllocateAction = (
{ dataTierAllocationType, allocationNodeAttribute }: DataAllocationMetaFields,
@@ -165,5 +164,22 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => (
}
}
+ /**
+ * DELETE PHASE SERIALIZATION
+ */
+ if (policy.phases.delete) {
+ if (policy.phases.delete.min_age) {
+ policy.phases.delete.min_age = `${policy.phases.delete.min_age}${_meta.delete.minAgeUnit}`;
+ }
+
+ if (originalPolicy?.phases.delete?.actions) {
+ const { wait_for_snapshot: __, ...rest } = originalPolicy.phases.delete.actions;
+ policy.phases.delete.actions = {
+ ...policy.phases.delete.actions,
+ ...rest,
+ };
+ }
+ }
+
return policy;
};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts
similarity index 50%
rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts
index 9c855ccb41624..f2e26a552efc9 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts
@@ -4,13 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fieldValidators, ValidationFunc } from '../../../shared_imports';
+import { fieldValidators, ValidationFunc, ValidationConfig } from '../../../../shared_imports';
-import { ROLLOVER_FORM_PATHS } from './constants';
+import { ROLLOVER_FORM_PATHS } from '../constants';
-import { i18nTexts } from './i18n_texts';
+import { i18nTexts } from '../i18n_texts';
+import { PolicyFromES } from '../../../../../common/types';
+import { FormInternal } from '../types';
-const { numberGreaterThanField } = fieldValidators;
+const { numberGreaterThanField, containsCharsField, emptyField, startsWithField } = fieldValidators;
const createIfNumberExistsValidator = ({
than,
@@ -46,7 +48,7 @@ export const ifExistsNumberNonNegative = createIfNumberExistsValidator({
* A special validation type used to keep track of validation errors for
* the rollover threshold values not being set (e.g., age and doc count)
*/
-export const ROLLOVER_EMPTY_VALIDATION = 'EMPTY';
+export const ROLLOVER_EMPTY_VALIDATION = 'ROLLOVER_EMPTY_VALIDATION';
/**
* An ILM policy requires that for rollover a value must be set for one of the threshold values.
@@ -87,3 +89,68 @@ export const rolloverThresholdsValidator: ValidationFunc = ({ form }) => {
fields[ROLLOVER_FORM_PATHS.maxSize].clearErrors(ROLLOVER_EMPTY_VALIDATION);
}
};
+
+export const minAgeValidator: ValidationFunc = (arg) =>
+ numberGreaterThanField({
+ than: 0,
+ allowEquality: true,
+ message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired,
+ })({
+ ...arg,
+ value: arg.value === '' ? -Infinity : parseInt(arg.value, 10),
+ });
+
+export const createPolicyNameValidations = ({
+ policies,
+ saveAsNewPolicy,
+ originalPolicyName,
+}: {
+ policies: PolicyFromES[];
+ saveAsNewPolicy: boolean;
+ originalPolicyName?: string;
+}): Array> => {
+ return [
+ {
+ validator: emptyField(i18nTexts.editPolicy.errors.policyNameRequiredMessage),
+ },
+ {
+ validator: startsWithField({
+ message: i18nTexts.editPolicy.errors.policyNameStartsWithUnderscoreErrorMessage,
+ char: '_',
+ }),
+ },
+ {
+ validator: containsCharsField({
+ message: i18nTexts.editPolicy.errors.policyNameContainsInvalidChars,
+ chars: [',', ' '],
+ }),
+ },
+ {
+ validator: (arg) => {
+ const policyName = arg.value;
+ if (window.TextEncoder && new window.TextEncoder().encode(policyName).length > 255) {
+ return {
+ message: i18nTexts.editPolicy.errors.policyNameTooLongErrorMessage,
+ };
+ }
+ },
+ },
+ {
+ validator: (arg) => {
+ const policyName = arg.value;
+ if (saveAsNewPolicy && policyName === originalPolicyName) {
+ return {
+ message: i18nTexts.editPolicy.errors.policyNameMustBeDifferentErrorMessage,
+ };
+ } else if (policyName !== originalPolicyName) {
+ const policyNames = policies.map((existingPolicy) => existingPolicy.name);
+ if (policyNames.includes(policyName)) {
+ return {
+ message: i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage,
+ };
+ }
+ }
+ },
+ },
+ ];
+};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts
index 1fba69b7634ae..ccd5d3a568fe3 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts
@@ -98,6 +98,42 @@ export const i18nTexts = {
defaultMessage: 'Only non-negative numbers are allowed.',
}
),
+ policyNameContainsInvalidChars: i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.errors.policyNameContainsInvalidCharsError',
+ {
+ defaultMessage: 'A policy name cannot contain spaces or commas.',
+ }
+ ),
+ policyNameAlreadyUsedErrorMessage: i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.policyNameAlreadyUsedError',
+ {
+ defaultMessage: 'That policy name is already used.',
+ }
+ ),
+ policyNameMustBeDifferentErrorMessage: i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.differentPolicyNameRequiredError',
+ {
+ defaultMessage: 'The policy name must be different.',
+ }
+ ),
+ policyNameRequiredMessage: i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.policyNameRequiredError',
+ {
+ defaultMessage: 'A policy name is required.',
+ }
+ ),
+ policyNameStartsWithUnderscoreErrorMessage: i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.policyNameStartsWithUnderscoreError',
+ {
+ defaultMessage: 'A policy name cannot start with an underscore.',
+ }
+ ),
+ policyNameTooLongErrorMessage: i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.policyNameTooLongError',
+ {
+ defaultMessage: 'A policy name cannot be longer than 255 bytes.',
+ }
+ ),
},
},
};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_save.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/save_policy.ts
similarity index 84%
rename from x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_save.ts
rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/save_policy.ts
index 9cf622e830cb2..e2ab6a8817ef6 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_save.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/save_policy.ts
@@ -3,23 +3,22 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
import { i18n } from '@kbn/i18n';
import { METRIC_TYPE } from '@kbn/analytics';
import { SerializedPolicy } from '../../../../common/types';
-import { savePolicy as savePolicyApi } from '../api';
-import { showApiError } from '../api_errors';
-import { getUiMetricsForPhases, trackUiMetric } from '../ui_metric';
+
import { UIM_POLICY_CREATE, UIM_POLICY_UPDATE } from '../../constants';
-import { toasts } from '../notification';
+
+import { toasts } from '../../services/notification';
+import { savePolicy as savePolicyApi } from '../../services/api';
+import { getUiMetricsForPhases, trackUiMetric } from '../../services/ui_metric';
+import { showApiError } from '../../services/api_errors';
export const savePolicy = async (
- readSerializedPolicy: () => SerializedPolicy,
+ serializedPolicy: SerializedPolicy,
isNew: boolean
): Promise => {
- const serializedPolicy = readSerializedPolicy();
-
try {
await savePolicyApi(serializedPolicy);
} catch (err) {
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts
index 1884f8dbc0619..dc3d8a640e682 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts
@@ -38,6 +38,10 @@ interface ColdPhaseMetaFields extends DataAllocationMetaFields, MinAgeField {
freezeEnabled: boolean;
}
+interface DeletePhaseMetaFields extends MinAgeField {
+ enabled: boolean;
+}
+
/**
* Describes the shape of data after deserialization.
*/
@@ -50,5 +54,6 @@ export interface FormInternal extends SerializedPolicy {
hot: HotPhaseMetaFields;
warm: WarmPhaseMetaFields;
cold: ColdPhaseMetaFields;
+ delete: DeletePhaseMetaFields;
};
}
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx
index 7b6521fd8a9ef..09c81efe163b5 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx
@@ -35,7 +35,7 @@ import { RouteComponentProps } from 'react-router-dom';
import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
import { getIndexListUri } from '../../../../../../index_management/public';
import { PolicyFromES } from '../../../../../common/types';
-import { getPolicyPath } from '../../../services/navigation';
+import { getPolicyEditPath } from '../../../services/navigation';
import { sortTable } from '../../../services';
import { trackUiMetric } from '../../../services/ui_metric';
@@ -229,7 +229,7 @@ export const TableContent: React.FunctionComponent = ({
return (
+ {...reactRouterNavigate(history, getPolicyEditPath(value as string), () =>
trackUiMetric(METRIC_TYPE.CLICK, UIM_EDIT_CLICK)
)}
>
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx
index 8f8b76602ac57..bcbb223ba095a 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx
@@ -4,12 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useEffect } from 'react';
import { ApplicationStart } from 'kibana/public';
import { RouteComponentProps } from 'react-router-dom';
import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { PolicyTable as PresentationComponent } from './policy_table';
+import { useKibana } from '../../../shared_imports';
import { useLoadPoliciesList } from '../../services/api';
interface Props {
@@ -20,8 +21,15 @@ export const PolicyTable: React.FunctionComponent =
navigateToApp,
history,
}) => {
+ const {
+ services: { breadcrumbService },
+ } = useKibana();
const { data: policies, isLoading, error, resendRequest } = useLoadPoliciesList(true);
+ useEffect(() => {
+ breadcrumbService.setBreadcrumbs('policies');
+ }, [breadcrumbService]);
+
if (isLoading) {
return (
= ({
const createPolicyButton = (
{
+ const breadcrumbService = new BreadcrumbService();
+ breadcrumbService.setup(jest.fn());
+ return breadcrumbService;
+};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/breadcrumbs.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/breadcrumbs.ts
new file mode 100644
index 0000000000000..7f9a5b8a3dab1
--- /dev/null
+++ b/x-pack/plugins/index_lifecycle_management/public/application/services/breadcrumbs.ts
@@ -0,0 +1,67 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { ChromeBreadcrumb } from 'kibana/public';
+import { ManagementAppMountParams } from '../../../../../../src/plugins/management/public';
+
+type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs'];
+
+// Build the breadcrumbs for this app
+const breadcrumbs = (function () {
+ const policies: ChromeBreadcrumb[] = [
+ {
+ text: i18n.translate('xpack.indexLifecycleMgmt.breadcrumb.homeLabel', {
+ defaultMessage: 'Index Lifecycle Management',
+ }),
+ href: `/policies`,
+ },
+ ];
+
+ const editPolicy: ChromeBreadcrumb[] = [
+ ...policies,
+ {
+ text: i18n.translate('xpack.indexLifecycleMgmt.breadcrumb.editPolicyLabel', {
+ defaultMessage: 'Edit policy',
+ }),
+ href: undefined,
+ },
+ ];
+
+ return {
+ policies,
+ editPolicy,
+ };
+})();
+
+export class BreadcrumbService {
+ private setBreadcrumbsHandler?: SetBreadcrumbs;
+
+ public setup(setBreadcrumbsHandler: SetBreadcrumbs): void {
+ this.setBreadcrumbsHandler = setBreadcrumbsHandler;
+ }
+
+ public setBreadcrumbs(type: keyof typeof breadcrumbs): void {
+ if (!this.setBreadcrumbsHandler) {
+ throw new Error(`BreadcrumbService#setup() must be called first!`);
+ }
+
+ const newBreadcrumbs = breadcrumbs[type] ? [...breadcrumbs[type]] : [...breadcrumbs.policies];
+
+ // Pop off last breadcrumb
+ const lastBreadcrumb = newBreadcrumbs.pop() as {
+ text: string;
+ href?: string;
+ };
+
+ // Put last breadcrumb back without href
+ newBreadcrumbs.push({
+ ...lastBreadcrumb,
+ href: undefined,
+ });
+
+ this.setBreadcrumbsHandler(newBreadcrumbs);
+ }
+}
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/documentation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/documentation.ts
index d459d304d5c71..b80de8a1477a0 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/documentation.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/services/documentation.ts
@@ -4,6 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
+/**
+ * TODO:
+ * IMPORTANT: Please see how {@link BreadcrumbService} is set up for an example of how these services should be set up
+ * in future. The pattern in this file is legacy and should be updated to conform to the plugin lifecycle.
+ */
+
export let skippingDisconnectedClustersUrl: string;
export let remoteClustersUrl: string;
export let transportPortUrl: string;
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/http.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/http.ts
index d61ed1ad25dde..b7761aec3fb8e 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/http.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/services/http.ts
@@ -4,6 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
+/**
+ * TODO:
+ * IMPORTANT: Please see how {@link BreadcrumbService} is set up for an example of how these services should be set up
+ * in future. The pattern in this file is legacy and should be updated to conform to the plugin lifecycle.
+ */
+
import { HttpSetup } from 'src/core/public';
import {
UseRequestConfig,
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts
index 72e9d51d8fdeb..3c88ecd6b8007 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts
@@ -4,6 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const getPolicyPath = (policyName: string): string => {
+export const ROUTES = {
+ list: '/policies',
+ edit: '/policies/edit/:policyName?',
+ create: '/policies/edit',
+};
+
+export const getPolicyEditPath = (policyName: string): string => {
return encodeURI(`/policies/edit/${encodeURIComponent(policyName)}`);
};
+
+export const getPolicyCreatePath = () => {
+ return ROUTES.create;
+};
+
+export const getPoliciesListPath = () => {
+ return ROUTES.list;
+};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/notification.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/notification.ts
index aa3ac9ea75c22..e8ef2d9b22443 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/notification.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/services/notification.ts
@@ -4,6 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
+/**
+ * TODO:
+ * IMPORTANT: Please see how {@link BreadcrumbService} is set up for an example of how these services should be set up
+ * in future. The pattern in this file is legacy and should be updated to conform to the plugin lifecycle.
+ */
+
import { IToasts, FatalErrorsSetup } from 'src/core/public';
export let toasts: IToasts;
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts
deleted file mode 100644
index 6ada039d45cd9..0000000000000
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts
+++ /dev/null
@@ -1,88 +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 { DeletePhase, SerializedDeletePhase } from '../../../../common/types';
-import { serializedPhaseInitialization } from '../../constants';
-import { isNumber, splitSizeAndUnits } from './policy_serialization';
-import {
- numberRequiredMessage,
- PhaseValidationErrors,
- positiveNumberRequiredMessage,
-} from './policy_validation';
-
-const deletePhaseInitialization: DeletePhase = {
- phaseEnabled: false,
- selectedMinimumAge: '0',
- selectedMinimumAgeUnits: 'd',
- waitForSnapshotPolicy: '',
-};
-
-export const deletePhaseFromES = (phaseSerialized?: SerializedDeletePhase): DeletePhase => {
- const phase = { ...deletePhaseInitialization };
- if (phaseSerialized === undefined || phaseSerialized === null) {
- return phase;
- }
-
- phase.phaseEnabled = true;
- if (phaseSerialized.min_age) {
- const { size: minAge, units: minAgeUnits } = splitSizeAndUnits(phaseSerialized.min_age);
- phase.selectedMinimumAge = minAge;
- phase.selectedMinimumAgeUnits = minAgeUnits;
- }
-
- if (phaseSerialized.actions) {
- const actions = phaseSerialized.actions;
-
- if (actions.wait_for_snapshot) {
- phase.waitForSnapshotPolicy = actions.wait_for_snapshot.policy;
- }
- }
-
- return phase;
-};
-
-export const deletePhaseToES = (
- phase: DeletePhase,
- originalEsPhase?: SerializedDeletePhase
-): SerializedDeletePhase => {
- if (!originalEsPhase) {
- originalEsPhase = { ...serializedPhaseInitialization };
- }
- const esPhase = { ...originalEsPhase };
-
- if (isNumber(phase.selectedMinimumAge)) {
- esPhase.min_age = `${phase.selectedMinimumAge}${phase.selectedMinimumAgeUnits}`;
- }
-
- esPhase.actions = esPhase.actions ? { ...esPhase.actions } : {};
-
- if (phase.waitForSnapshotPolicy) {
- esPhase.actions.wait_for_snapshot = {
- policy: phase.waitForSnapshotPolicy,
- };
- } else {
- delete esPhase.actions.wait_for_snapshot;
- }
-
- return esPhase;
-};
-
-export const validateDeletePhase = (phase: DeletePhase): PhaseValidationErrors => {
- if (!phase.phaseEnabled) {
- return {};
- }
-
- const phaseErrors = {} as PhaseValidationErrors;
-
- // min age needs to be a positive number
- if (!isNumber(phase.selectedMinimumAge)) {
- phaseErrors.selectedMinimumAge = [numberRequiredMessage];
- } else if (parseInt(phase.selectedMinimumAge, 10) < 0) {
- phaseErrors.selectedMinimumAge = [positiveNumberRequiredMessage];
- }
-
- return { ...phaseErrors };
-};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts
deleted file mode 100644
index 19481b39a2c80..0000000000000
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts
+++ /dev/null
@@ -1,198 +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.
- */
-// Prefer importing entire lodash library, e.g. import { get } from "lodash"
-// eslint-disable-next-line no-restricted-imports
-import cloneDeep from 'lodash/cloneDeep';
-import { deserializePolicy, legacySerializePolicy } from './policy_serialization';
-import { defaultNewDeletePhase } from '../../constants';
-
-describe('Policy serialization', () => {
- test('serialize a policy using "default" data allocation', () => {
- expect(
- legacySerializePolicy(
- {
- name: 'test',
- phases: {
- delete: { ...defaultNewDeletePhase },
- },
- },
- {
- name: 'test',
- phases: {
- hot: { actions: {} },
- },
- }
- )
- ).toEqual({
- name: 'test',
- phases: {},
- });
- });
-
- test('serialize a policy using "custom" data allocation', () => {
- expect(
- legacySerializePolicy(
- {
- name: 'test',
- phases: {
- delete: { ...defaultNewDeletePhase },
- },
- },
- {
- name: 'test',
- phases: {
- hot: { actions: {} },
- },
- }
- )
- ).toEqual({
- name: 'test',
- phases: {},
- });
- });
-
- test('serialize a policy using "custom" data allocation with no node attributes', () => {
- expect(
- legacySerializePolicy(
- {
- name: 'test',
- phases: {
- delete: { ...defaultNewDeletePhase },
- },
- },
- {
- name: 'test',
- phases: {
- hot: { actions: {} },
- },
- }
- )
- ).toEqual({
- // There should be no allocation action in any phases...
- name: 'test',
- phases: {},
- });
- });
-
- test('serialize a policy using "none" data allocation with no node attributes', () => {
- expect(
- legacySerializePolicy(
- {
- name: 'test',
- phases: {
- delete: { ...defaultNewDeletePhase },
- },
- },
- {
- name: 'test',
- phases: {
- hot: { actions: {} },
- },
- }
- )
- ).toEqual({
- // There should be no allocation action in any phases...
- name: 'test',
- phases: {},
- });
- });
-
- test('serialization does not alter the original policy', () => {
- const originalPolicy = {
- name: 'test',
- phases: {},
- };
-
- const originalClone = cloneDeep(originalPolicy);
-
- const deserializedPolicy = {
- name: 'test',
- phases: {
- delete: { ...defaultNewDeletePhase },
- },
- };
-
- legacySerializePolicy(deserializedPolicy, originalPolicy);
- expect(originalPolicy).toEqual(originalClone);
- });
-
- test('serialize a policy using "best_compression" codec for forcemerge', () => {
- expect(
- legacySerializePolicy(
- {
- name: 'test',
- phases: {
- delete: { ...defaultNewDeletePhase },
- },
- },
- {
- name: 'test',
- phases: {
- hot: { actions: {} },
- },
- }
- )
- ).toEqual({
- name: 'test',
- phases: {},
- });
- });
-
- test('de-serialize a policy using "best_compression" codec for forcemerge', () => {
- expect(
- deserializePolicy({
- modified_date: Date.now().toString(),
- name: 'test',
- version: 1,
- policy: {
- name: 'test',
- phases: {
- hot: {
- actions: {
- rollover: {
- max_age: '30d',
- max_size: '50gb',
- },
- forcemerge: {
- max_num_segments: 1,
- index_codec: 'best_compression',
- },
- set_priority: {
- priority: 100,
- },
- },
- },
- },
- },
- })
- ).toEqual({
- name: 'test',
- phases: {
- delete: { ...defaultNewDeletePhase },
- },
- });
- });
-
- test('delete "best_compression" codec for forcemerge if disabled in UI', () => {
- expect(
- legacySerializePolicy(
- {
- name: 'test',
- phases: {
- delete: { ...defaultNewDeletePhase },
- },
- },
- {
- name: 'test',
- phases: {},
- }
- )
- ).toEqual({
- name: 'test',
- phases: {},
- });
- });
-});
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts
deleted file mode 100644
index 55e9d88dcd383..0000000000000
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts
+++ /dev/null
@@ -1,82 +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 { LegacyPolicy, PolicyFromES, SerializedPolicy } from '../../../../common/types';
-
-import { defaultNewDeletePhase, serializedPhaseInitialization } from '../../constants';
-
-import { deletePhaseFromES, deletePhaseToES } from './delete_phase';
-
-export const splitSizeAndUnits = (field: string): { size: string; units: string } => {
- let size = '';
- let units = '';
-
- const result = /(\d+)(\w+)/.exec(field);
- if (result) {
- size = result[1];
- units = result[2];
- }
-
- return {
- size,
- units,
- };
-};
-
-export const isNumber = (value: any): boolean => value !== '' && value !== null && isFinite(value);
-
-export const getPolicyByName = (
- policies: PolicyFromES[] | null | undefined,
- policyName: string = ''
-): PolicyFromES | undefined => {
- if (policies && policies.length > 0) {
- return policies.find((policy: PolicyFromES) => policy.name === policyName);
- }
-};
-
-export const initializeNewPolicy = (newPolicyName: string = ''): LegacyPolicy => {
- return {
- name: newPolicyName,
- phases: {
- delete: { ...defaultNewDeletePhase },
- },
- };
-};
-
-export const deserializePolicy = (policy: PolicyFromES): LegacyPolicy => {
- const {
- name,
- policy: { phases },
- } = policy;
-
- return {
- name,
- phases: {
- delete: deletePhaseFromES(phases.delete),
- },
- };
-};
-
-export const legacySerializePolicy = (
- policy: LegacyPolicy,
- originalEsPolicy: SerializedPolicy = {
- name: policy.name,
- phases: { hot: { ...serializedPhaseInitialization } },
- }
-): SerializedPolicy => {
- const serializedPolicy = {
- name: policy.name,
- phases: {},
- } as SerializedPolicy;
-
- if (policy.phases.delete.phaseEnabled) {
- serializedPolicy.phases.delete = deletePhaseToES(
- policy.phases.delete,
- originalEsPolicy.phases.delete
- );
- }
- return serializedPolicy;
-};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts
deleted file mode 100644
index 79c909c433f33..0000000000000
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts
+++ /dev/null
@@ -1,144 +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 { i18n } from '@kbn/i18n';
-import { DeletePhase, LegacyPolicy, PolicyFromES } from '../../../../common/types';
-import { validateDeletePhase } from './delete_phase';
-
-export const propertyof = (propertyName: keyof T & string) => propertyName;
-
-export const numberRequiredMessage = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.numberRequiredError',
- {
- defaultMessage: 'A number is required.',
- }
-);
-
-// TODO validation includes 0 -> should be non-negative number?
-export const positiveNumberRequiredMessage = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.positiveNumberRequiredError',
- {
- defaultMessage: 'Only positive numbers are allowed.',
- }
-);
-
-export const positiveNumbersAboveZeroErrorMessage = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.positiveNumberAboveZeroRequiredError',
- {
- defaultMessage: 'Only numbers above 0 are allowed.',
- }
-);
-
-export const policyNameRequiredMessage = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.policyNameRequiredError',
- {
- defaultMessage: 'A policy name is required.',
- }
-);
-
-export const policyNameStartsWithUnderscoreErrorMessage = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.policyNameStartsWithUnderscoreError',
- {
- defaultMessage: 'A policy name cannot start with an underscore.',
- }
-);
-export const policyNameContainsCommaErrorMessage = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.policyNameContainsCommaError',
- {
- defaultMessage: 'A policy name cannot include a comma.',
- }
-);
-export const policyNameContainsSpaceErrorMessage = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.policyNameContainsSpaceError',
- {
- defaultMessage: 'A policy name cannot include a space.',
- }
-);
-
-export const policyNameTooLongErrorMessage = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.policyNameTooLongError',
- {
- defaultMessage: 'A policy name cannot be longer than 255 bytes.',
- }
-);
-export const policyNameMustBeDifferentErrorMessage = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.differentPolicyNameRequiredError',
- {
- defaultMessage: 'The policy name must be different.',
- }
-);
-export const policyNameAlreadyUsedErrorMessage = i18n.translate(
- 'xpack.indexLifecycleMgmt.editPolicy.policyNameAlreadyUsedError',
- {
- defaultMessage: 'That policy name is already used.',
- }
-);
-export type PhaseValidationErrors = {
- [P in keyof Partial]: string[];
-};
-
-export interface ValidationErrors {
- delete: PhaseValidationErrors;
- policyName: string[];
-}
-
-export const validatePolicy = (
- saveAsNew: boolean,
- policy: LegacyPolicy,
- policies: PolicyFromES[],
- originalPolicyName: string
-): [boolean, ValidationErrors] => {
- const policyNameErrors: string[] = [];
- if (!policy.name) {
- policyNameErrors.push(policyNameRequiredMessage);
- } else {
- if (policy.name.startsWith('_')) {
- policyNameErrors.push(policyNameStartsWithUnderscoreErrorMessage);
- }
- if (policy.name.includes(',')) {
- policyNameErrors.push(policyNameContainsCommaErrorMessage);
- }
- if (policy.name.includes(' ')) {
- policyNameErrors.push(policyNameContainsSpaceErrorMessage);
- }
- if (window.TextEncoder && new window.TextEncoder().encode(policy.name).length > 255) {
- policyNameErrors.push(policyNameTooLongErrorMessage);
- }
-
- if (saveAsNew && policy.name === originalPolicyName) {
- policyNameErrors.push(policyNameMustBeDifferentErrorMessage);
- } else if (policy.name !== originalPolicyName) {
- const policyNames = policies.map((existingPolicy) => existingPolicy.name);
- if (policyNames.includes(policy.name)) {
- policyNameErrors.push(policyNameAlreadyUsedErrorMessage);
- }
- }
- }
-
- const deletePhaseErrors = validateDeletePhase(policy.phases.delete);
- const isValid = policyNameErrors.length === 0 && Object.keys(deletePhaseErrors).length === 0;
- return [
- isValid,
- {
- policyName: [...policyNameErrors],
- delete: deletePhaseErrors,
- },
- ];
-};
-
-export const findFirstError = (errors?: ValidationErrors): string | undefined => {
- if (!errors) {
- return;
- }
-
- if (errors.policyName.length > 0) {
- return propertyof('policyName');
- }
-
- if (Object.keys(errors.delete).length > 0) {
- return `${propertyof('delete')}.${Object.keys(errors.delete)[0]}`;
- }
-};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts
index 274d3d1ca97f3..a94c0a8b8ef59 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts
@@ -4,6 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
+/**
+ * TODO:
+ * IMPORTANT: Please see how {@link BreadcrumbService} is set up for an example of how these services should be set up
+ * in future. The pattern in this file is legacy and should be updated to conform to the plugin lifecycle.
+ */
+
import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
import { UiStatsMetricType } from '@kbn/analytics';
diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx
index afdf726ea02f9..80c8e1414e1f8 100644
--- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx
@@ -25,7 +25,7 @@ import {
} from '@elastic/eui';
import { ApplicationStart } from 'kibana/public';
-import { getPolicyPath } from '../../application/services/navigation';
+import { getPolicyEditPath } from '../../application/services/navigation';
import { Index, IndexLifecyclePolicy } from '../../../common/types';
const getHeaders = (): Array<[keyof IndexLifecyclePolicy, string]> => {
@@ -192,7 +192,7 @@ export class IndexLifecycleSummary extends Component {
content = (
{value}
diff --git a/x-pack/plugins/index_lifecycle_management/public/index.ts b/x-pack/plugins/index_lifecycle_management/public/index.ts
index 586763188a54b..2aee76cd8b136 100644
--- a/x-pack/plugins/index_lifecycle_management/public/index.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/index.ts
@@ -12,3 +12,5 @@ import { IndexLifecycleManagementPlugin } from './plugin';
export const plugin = (initializerContext: PluginInitializerContext) => {
return new IndexLifecycleManagementPlugin(initializerContext);
};
+
+export { ILM_URL_GENERATOR_ID, IlmUrlGeneratorState } from './url_generator';
diff --git a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx
index 24ce036c0e058..deef5cfe6ef2c 100644
--- a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx
@@ -12,13 +12,17 @@ import { init as initHttp } from './application/services/http';
import { init as initDocumentation } from './application/services/documentation';
import { init as initUiMetric } from './application/services/ui_metric';
import { init as initNotification } from './application/services/notification';
+import { BreadcrumbService } from './application/services/breadcrumbs';
import { addAllExtensions } from './extend_index_management';
-import { PluginsDependencies, ClientConfigType } from './types';
+import { ClientConfigType, SetupDependencies } from './types';
+import { registerUrlGenerator } from './url_generator';
export class IndexLifecycleManagementPlugin {
constructor(private readonly initializerContext: PluginInitializerContext) {}
- public setup(coreSetup: CoreSetup, plugins: PluginsDependencies) {
+ private breadcrumbService = new BreadcrumbService();
+
+ public setup(coreSetup: CoreSetup, plugins: SetupDependencies) {
const {
ui: { enabled: isIndexLifecycleManagementUiEnabled },
} = this.initializerContext.config.get();
@@ -31,7 +35,7 @@ export class IndexLifecycleManagementPlugin {
getStartServices,
} = coreSetup;
- const { usageCollection, management, indexManagement, home, cloud } = plugins;
+ const { usageCollection, management, indexManagement, home, cloud, share } = plugins;
// Initialize services even if the app isn't mounted, because they're used by index management extensions.
initHttp(http);
@@ -42,7 +46,7 @@ export class IndexLifecycleManagementPlugin {
id: PLUGIN.ID,
title: PLUGIN.TITLE,
order: 2,
- mount: async ({ element, history }) => {
+ mount: async ({ element, history, setBreadcrumbs }) => {
const [coreStart] = await getStartServices();
const {
chrome: { docTitle },
@@ -52,6 +56,7 @@ export class IndexLifecycleManagementPlugin {
} = coreStart;
docTitle.change(PLUGIN.TITLE);
+ this.breadcrumbService.setup(setBreadcrumbs);
// Initialize additional services.
initDocumentation(
@@ -66,6 +71,7 @@ export class IndexLifecycleManagementPlugin {
history,
navigateToApp,
getUrlForApp,
+ this.breadcrumbService,
cloud
);
@@ -97,6 +103,8 @@ export class IndexLifecycleManagementPlugin {
if (indexManagement) {
addAllExtensions(indexManagement.extensionsService);
}
+
+ registerUrlGenerator(coreSetup, management, share);
}
}
diff --git a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts
index 023aeba57aa7a..a127574d5bad0 100644
--- a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts
@@ -18,6 +18,7 @@ export {
getFieldValidityAndErrorMessage,
useFormContext,
FormSchema,
+ ValidationConfig,
} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers';
@@ -27,6 +28,8 @@ export {
NumericField,
SelectField,
SuperSelectField,
+ ComboBoxField,
+ TextField,
} from '../../../../src/plugins/es_ui_shared/static/forms/components';
export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public';
diff --git a/x-pack/plugins/index_lifecycle_management/public/types.ts b/x-pack/plugins/index_lifecycle_management/public/types.ts
index c9b9b063cd45f..1ce43957b1444 100644
--- a/x-pack/plugins/index_lifecycle_management/public/types.ts
+++ b/x-pack/plugins/index_lifecycle_management/public/types.ts
@@ -9,13 +9,17 @@ import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/p
import { ManagementSetup } from '../../../../src/plugins/management/public';
import { IndexManagementPluginSetup } from '../../index_management/public';
import { CloudSetup } from '../../cloud/public';
+import { SharePluginSetup } from '../../../../src/plugins/share/public';
-export interface PluginsDependencies {
+import { BreadcrumbService } from './application/services/breadcrumbs';
+
+export interface SetupDependencies {
usageCollection?: UsageCollectionSetup;
management: ManagementSetup;
cloud?: CloudSetup;
indexManagement?: IndexManagementPluginSetup;
home?: HomePublicPluginSetup;
+ share: SharePluginSetup;
}
export interface ClientConfigType {
@@ -25,5 +29,6 @@ export interface ClientConfigType {
}
export interface AppServicesContext {
+ breadcrumbService: BreadcrumbService;
cloud?: CloudSetup;
}
diff --git a/x-pack/plugins/index_lifecycle_management/public/url_generator.ts b/x-pack/plugins/index_lifecycle_management/public/url_generator.ts
new file mode 100644
index 0000000000000..a884c9a54a4b8
--- /dev/null
+++ b/x-pack/plugins/index_lifecycle_management/public/url_generator.ts
@@ -0,0 +1,60 @@
+/*
+ * 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 { CoreSetup } from 'kibana/public';
+import { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public/';
+import {
+ getPoliciesListPath,
+ getPolicyCreatePath,
+ getPolicyEditPath,
+} from './application/services/navigation';
+import { MANAGEMENT_APP_ID } from '../../../../src/plugins/management/public';
+import { SetupDependencies } from './types';
+import { PLUGIN } from '../common/constants';
+
+export const ILM_URL_GENERATOR_ID = 'ILM_URL_GENERATOR_ID';
+
+export interface IlmUrlGeneratorState {
+ page: 'policies_list' | 'policy_edit' | 'policy_create';
+ policyName?: string;
+ absolute?: boolean;
+}
+export const createIlmUrlGenerator = (
+ getAppBasePath: (absolute?: boolean) => Promise
+): UrlGeneratorsDefinition => {
+ return {
+ id: ILM_URL_GENERATOR_ID,
+ createUrl: async (state: IlmUrlGeneratorState): Promise => {
+ switch (state.page) {
+ case 'policy_create': {
+ return `${await getAppBasePath(!!state.absolute)}${getPolicyCreatePath()}`;
+ }
+ case 'policy_edit': {
+ return `${await getAppBasePath(!!state.absolute)}${getPolicyEditPath(state.policyName!)}`;
+ }
+ case 'policies_list': {
+ return `${await getAppBasePath(!!state.absolute)}${getPoliciesListPath()}`;
+ }
+ }
+ },
+ };
+};
+
+export const registerUrlGenerator = (
+ coreSetup: CoreSetup,
+ management: SetupDependencies['management'],
+ share: SetupDependencies['share']
+) => {
+ const getAppBasePath = async (absolute = false) => {
+ const [coreStart] = await coreSetup.getStartServices();
+ return coreStart.application.getUrlForApp(MANAGEMENT_APP_ID, {
+ path: management.sections.section.data.getApp(PLUGIN.ID)!.basePath,
+ absolute,
+ });
+ };
+
+ share.urlGenerators.registerUrlGenerator(createIlmUrlGenerator(getAppBasePath));
+};
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
index 8a610a04f8bb1..b5386dec34205 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
@@ -13,13 +13,13 @@ export type TestSubjects =
| 'createTemplateButton'
| 'dataStreamsEmptyPromptTemplateLink'
| 'dataStreamTable'
- | 'dataStreamTable'
| 'deleteSystemTemplateCallOut'
| 'deleteTemplateButton'
| 'deleteTemplatesConfirmation'
| 'documentationLink'
| 'emptyPrompt'
| 'filterList.filterItem'
+ | 'ilmPolicyLink'
| 'includeStatsSwitch'
| 'indexTable'
| 'indexTableIncludeHiddenIndicesToggle'
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
index 82bd858240e1e..6bf6c11a37bb4 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
@@ -7,6 +7,7 @@
import { act } from 'react-dom/test-utils';
import { ReactWrapper } from 'enzyme';
+import { EuiDescriptionListDescription } from '@elastic/eui';
import {
registerTestBed,
TestBed,
@@ -26,15 +27,17 @@ export interface DataStreamsTabTestBed extends TestBed {
clickReloadButton: () => void;
clickNameAt: (index: number) => void;
clickIndicesAt: (index: number) => void;
- clickDeletActionAt: (index: number) => void;
+ clickDeleteActionAt: (index: number) => void;
clickConfirmDelete: () => void;
- clickDeletDataStreamButton: () => void;
+ clickDeleteDataStreamButton: () => void;
};
findDeleteActionAt: (index: number) => ReactWrapper;
findDeleteConfirmationModal: () => ReactWrapper;
findDetailPanel: () => ReactWrapper;
findDetailPanelTitle: () => string;
findEmptyPromptIndexTemplateLink: () => ReactWrapper;
+ findDetailPanelIlmPolicyLink: () => ReactWrapper;
+ findDetailPanelIlmPolicyName: () => ReactWrapper;
}
export const setup = async (overridingDependencies: any = {}): Promise => {
@@ -115,7 +118,7 @@ export const setup = async (overridingDependencies: any = {}): Promise {
+ const clickDeleteActionAt = (index: number) => {
findDeleteActionAt(index).simulate('click');
};
@@ -135,7 +138,7 @@ export const setup = async (overridingDependencies: any = {}): Promise {
+ const clickDeleteDataStreamButton = () => {
const { find } = testBed;
find('deleteDataStreamButton').simulate('click');
};
@@ -150,6 +153,17 @@ export const setup = async (overridingDependencies: any = {}): Promise {
+ const { find } = testBed;
+ return find('ilmPolicyLink');
+ };
+
+ const findDetailPanelIlmPolicyName = () => {
+ const descriptionList = testBed.component.find(EuiDescriptionListDescription);
+ // ilm policy is the last in the details list
+ return descriptionList.last();
+ };
+
return {
...testBed,
actions: {
@@ -159,15 +173,17 @@ export const setup = async (overridingDependencies: any = {}): Promise {
]);
setLoadDataStreamResponse(dataStreamForDetailPanel);
- testBed = await setup();
+ testBed = await setup({ history: createMemoryHistory() });
await act(async () => {
testBed.actions.goToDataStreamsList();
});
@@ -176,19 +177,19 @@ describe('Data Streams tab', () => {
describe('deleting a data stream', () => {
test('shows a confirmation modal', async () => {
const {
- actions: { clickDeletActionAt },
+ actions: { clickDeleteActionAt },
findDeleteConfirmationModal,
} = testBed;
- clickDeletActionAt(0);
+ clickDeleteActionAt(0);
const confirmationModal = findDeleteConfirmationModal();
expect(confirmationModal).toBeDefined();
});
test('sends a request to the Delete API', async () => {
const {
- actions: { clickDeletActionAt, clickConfirmDelete },
+ actions: { clickDeleteActionAt, clickConfirmDelete },
} = testBed;
- clickDeletActionAt(0);
+ clickDeleteActionAt(0);
httpRequestsMockHelpers.setDeleteDataStreamResponse({
results: {
@@ -219,12 +220,12 @@ describe('Data Streams tab', () => {
test('deletes the data stream when delete button is clicked', async () => {
const {
- actions: { clickNameAt, clickDeletDataStreamButton, clickConfirmDelete },
+ actions: { clickNameAt, clickDeleteDataStreamButton, clickConfirmDelete },
} = testBed;
await clickNameAt(0);
- clickDeletDataStreamButton();
+ clickDeleteDataStreamButton();
httpRequestsMockHelpers.setDeleteDataStreamResponse({
results: {
@@ -263,7 +264,9 @@ describe('Data Streams tab', () => {
setLoadDataStreamsResponse([dataStreamDollarSign]);
setLoadDataStreamResponse(dataStreamDollarSign);
- testBed = await setup();
+ testBed = await setup({
+ history: createMemoryHistory(),
+ });
await act(async () => {
testBed.actions.goToDataStreamsList();
});
@@ -287,4 +290,82 @@ describe('Data Streams tab', () => {
});
});
});
+
+ describe('url generators', () => {
+ const mockIlmUrlGenerator = {
+ getUrlGenerator: () => ({
+ createUrl: ({ policyName }: { policyName: string }) => `/test/${policyName}`,
+ }),
+ };
+ test('with an ILM url generator and an ILM policy', async () => {
+ const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers;
+
+ const dataStreamForDetailPanel = {
+ ...createDataStreamPayload('dataStream1'),
+ ilmPolicyName: 'my_ilm_policy',
+ };
+ setLoadDataStreamsResponse([dataStreamForDetailPanel]);
+ setLoadDataStreamResponse(dataStreamForDetailPanel);
+
+ testBed = await setup({
+ history: createMemoryHistory(),
+ urlGenerators: mockIlmUrlGenerator,
+ });
+ await act(async () => {
+ testBed.actions.goToDataStreamsList();
+ });
+ testBed.component.update();
+
+ const { actions, findDetailPanelIlmPolicyLink } = testBed;
+ await actions.clickNameAt(0);
+ expect(findDetailPanelIlmPolicyLink().prop('href')).toBe('/test/my_ilm_policy');
+ });
+
+ test('with an ILM url generator and no ILM policy', async () => {
+ const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers;
+
+ const dataStreamForDetailPanel = createDataStreamPayload('dataStream1');
+ setLoadDataStreamsResponse([dataStreamForDetailPanel]);
+ setLoadDataStreamResponse(dataStreamForDetailPanel);
+
+ testBed = await setup({
+ history: createMemoryHistory(),
+ urlGenerators: mockIlmUrlGenerator,
+ });
+ await act(async () => {
+ testBed.actions.goToDataStreamsList();
+ });
+ testBed.component.update();
+
+ const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed;
+ await actions.clickNameAt(0);
+ expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy();
+ expect(findDetailPanelIlmPolicyName().contains('None')).toBeTruthy();
+ });
+
+ test('without an ILM url generator and with an ILM policy', async () => {
+ const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers;
+
+ const dataStreamForDetailPanel = {
+ ...createDataStreamPayload('dataStream1'),
+ ilmPolicyName: 'my_ilm_policy',
+ };
+ setLoadDataStreamsResponse([dataStreamForDetailPanel]);
+ setLoadDataStreamResponse(dataStreamForDetailPanel);
+
+ testBed = await setup({
+ history: createMemoryHistory(),
+ urlGenerators: { getUrlGenerator: () => {} },
+ });
+ await act(async () => {
+ testBed.actions.goToDataStreamsList();
+ });
+ testBed.component.update();
+
+ const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed;
+ await actions.clickNameAt(0);
+ expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy();
+ expect(findDetailPanelIlmPolicyName().contains('my_ilm_policy')).toBeTruthy();
+ });
+ });
});
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
index b660adb9eec08..c7af3a8f45105 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
@@ -26,8 +26,6 @@ const testBedConfig: TestBedConfig = {
doMountAsync: true,
};
-const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig);
-
export interface IndicesTestBed extends TestBed {
actions: {
selectIndexDetailsTab: (tab: 'settings' | 'mappings' | 'stats' | 'edit_settings') => void;
@@ -39,7 +37,11 @@ export interface IndicesTestBed extends TestBed {
findDataStreamDetailPanelTitle: () => string;
}
-export const setup = async (): Promise => {
+export const setup = async (overridingDependencies: any = {}): Promise => {
+ const initTestBed = registerTestBed(
+ WithAppDependencies(IndexManagementHome, overridingDependencies),
+ testBedConfig
+ );
const testBed = await initTestBed();
/**
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
index 3d6d94d165855..adc47515acaee 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
@@ -18,6 +18,7 @@ import { createDataStreamPayload } from './data_streams_tab.helpers';
at createWorker (//node_modules/brace/index.js:17992:5)
*/
import { stubWebWorker } from '../../../../../test_utils/stub_web_worker';
+import { createMemoryHistory } from 'history';
stubWebWorker();
describe(' ', () => {
@@ -75,7 +76,9 @@ describe(' ', () => {
httpRequestsMockHelpers.setLoadDataStreamResponse(createDataStreamPayload('dataStream1'));
- testBed = await setup();
+ testBed = await setup({
+ history: createMemoryHistory(),
+ });
await act(async () => {
const { component } = testBed;
diff --git a/x-pack/plugins/index_management/kibana.json b/x-pack/plugins/index_management/kibana.json
index 28846414ca2e8..ff6a71d53894a 100644
--- a/x-pack/plugins/index_management/kibana.json
+++ b/x-pack/plugins/index_management/kibana.json
@@ -7,7 +7,8 @@
"home",
"licensing",
"management",
- "features"
+ "features",
+ "share"
],
"optionalPlugins": [
"security",
diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx
index 22e6f09907d75..c654288aaceb8 100644
--- a/x-pack/plugins/index_management/public/application/app_context.tsx
+++ b/x-pack/plugins/index_management/public/application/app_context.tsx
@@ -14,6 +14,7 @@ import { IngestManagerSetup } from '../../../ingest_manager/public';
import { IndexMgmtMetricsType } from '../types';
import { UiMetricService, NotificationService, HttpService } from './services';
import { ExtensionsService } from '../services';
+import { SharePluginStart } from '../../../../../src/plugins/share/public';
const AppContext = createContext(undefined);
@@ -35,6 +36,7 @@ export interface AppDependencies {
history: ScopedHistory;
setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs'];
uiSettings: CoreSetup['uiSettings'];
+ urlGenerators: SharePluginStart['urlGenerators'];
}
export const AppContextProvider = ({
diff --git a/x-pack/plugins/index_management/public/application/constants/ilm_url_generator.ts b/x-pack/plugins/index_management/public/application/constants/ilm_url_generator.ts
new file mode 100644
index 0000000000000..1b71e389ee354
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/constants/ilm_url_generator.ts
@@ -0,0 +1,8 @@
+/*
+ * 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.
+ */
+
+export const ILM_URL_GENERATOR_ID = 'ILM_URL_GENERATOR_ID';
+export const ILM_PAGES_POLICY_EDIT = 'policy_edit';
diff --git a/x-pack/plugins/index_management/public/application/constants/index.ts b/x-pack/plugins/index_management/public/application/constants/index.ts
index d55d28dce7003..80c8779f2f020 100644
--- a/x-pack/plugins/index_management/public/application/constants/index.ts
+++ b/x-pack/plugins/index_management/public/application/constants/index.ts
@@ -15,3 +15,5 @@ export {
} from './detail_panel_tabs';
export const REACT_ROOT_ID = 'indexManagementReactRoot';
+
+export * from './ilm_url_generator';
diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts
index f7b728c875762..52528d3c51145 100644
--- a/x-pack/plugins/index_management/public/application/mount_management_section.ts
+++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts
@@ -12,7 +12,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
import { IngestManagerSetup } from '../../../ingest_manager/public';
import { PLUGIN } from '../../common/constants';
import { ExtensionsService } from '../services';
-import { IndexMgmtMetricsType } from '../types';
+import { IndexMgmtMetricsType, StartDependencies } from '../types';
import { AppDependencies } from './app_context';
import { breadcrumbService } from './services/breadcrumbs';
import { documentationService } from './services/documentation';
@@ -28,14 +28,14 @@ interface InternalServices {
}
export async function mountManagementSection(
- coreSetup: CoreSetup,
+ coreSetup: CoreSetup,
usageCollection: UsageCollectionSetup,
services: InternalServices,
params: ManagementAppMountParams,
ingestManager?: IngestManagerSetup
) {
const { element, setBreadcrumbs, history } = params;
- const [core] = await coreSetup.getStartServices();
+ const [core, startDependencies] = await coreSetup.getStartServices();
const {
docLinks,
fatalErrors,
@@ -44,6 +44,7 @@ export async function mountManagementSection(
uiSettings,
} = core;
+ const { urlGenerators } = startDependencies.share;
docTitle.change(PLUGIN.getI18nName(i18n));
breadcrumbService.setup(setBreadcrumbs);
@@ -62,6 +63,7 @@ export async function mountManagementSection(
history,
setBreadcrumbs,
uiSettings,
+ urlGenerators,
};
const unmountAppCallback = renderApp(element, { core, dependencies: appDependencies });
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
index 0af22b497a4c0..9ec6993717435 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
@@ -28,6 +28,10 @@ import { SectionLoading, SectionError, Error, DataHealth } from '../../../../com
import { useLoadDataStream } from '../../../../services/api';
import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal';
import { humanizeTimeStamp } from '../humanize_time_stamp';
+import { useUrlGenerator } from '../../../../services/use_url_generator';
+import { ILM_PAGES_POLICY_EDIT, ILM_URL_GENERATOR_ID } from '../../../../constants';
+import { useAppContext } from '../../../../app_context';
+import { getIndexListUri } from '../../../../..';
interface DetailsListProps {
details: Array<{
@@ -72,19 +76,26 @@ const DetailsList: React.FunctionComponent = ({ details }) =>
interface Props {
dataStreamName: string;
- backingIndicesLink: ReturnType;
onClose: (shouldReload?: boolean) => void;
}
export const DataStreamDetailPanel: React.FunctionComponent = ({
dataStreamName,
- backingIndicesLink,
onClose,
}) => {
const { error, data: dataStream, isLoading } = useLoadDataStream(dataStreamName);
const [isDeleting, setIsDeleting] = useState(false);
+ const ilmPolicyLink = useUrlGenerator({
+ urlGeneratorId: ILM_URL_GENERATOR_ID,
+ urlGeneratorState: {
+ page: ILM_PAGES_POLICY_EDIT,
+ policyName: dataStream?.ilmPolicyName,
+ },
+ });
+ const { history } = useAppContext();
+
let content;
if (isLoading) {
@@ -159,7 +170,16 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesToolTip', {
defaultMessage: `The data stream's current backing indices`,
}),
- content: {indices.length} ,
+ content: (
+
+ {indices.length}
+
+ ),
},
{
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldTitle', {
@@ -196,13 +216,23 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', {
defaultMessage: `The index lifecycle policy that manages the data stream's data`,
}),
- content: ilmPolicyName ?? (
-
- {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage', {
- defaultMessage: `None`,
- })}
-
- ),
+ content:
+ ilmPolicyName && ilmPolicyLink ? (
+
+ {ilmPolicyName}
+
+ ) : (
+ ilmPolicyName || (
+
+ {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage', {
+ defaultMessage: `None`,
+ })}
+
+ )
+ ),
},
];
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
index 19286523055f5..ba79319b566bf 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
@@ -28,7 +28,6 @@ import {
import { useAppContext } from '../../../app_context';
import { SectionError, SectionLoading, Error } from '../../../components';
import { useLoadDataStreams } from '../../../services/api';
-import { getIndexListUri } from '../../../services/routing';
import { documentationService } from '../../../services/documentation';
import { Section } from '../home';
import { DataStreamTable } from './data_stream_table';
@@ -233,10 +232,6 @@ export const DataStreamList: React.FunctionComponent {
history.push(`/${Section.DataStreams}`);
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
index 0c403e69d2e76..961a171f9f05a 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
@@ -19,9 +19,9 @@ import {
EuiCodeBlock,
EuiSpacer,
} from '@elastic/eui';
-import { useAppContext } from '../../../../../app_context';
import { TemplateDeserialized } from '../../../../../../../common';
-import { getILMPolicyPath } from '../../../../../services/routing';
+import { ILM_PAGES_POLICY_EDIT, ILM_URL_GENERATOR_ID } from '../../../../../constants';
+import { useUrlGenerator } from '../../../../../services/use_url_generator';
interface Props {
templateDetails: TemplateDeserialized;
@@ -53,9 +53,13 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails })
const numIndexPatterns = indexPatterns.length;
- const {
- core: { getUrlForApp },
- } = useAppContext();
+ const ilmPolicyLink = useUrlGenerator({
+ urlGeneratorId: ILM_URL_GENERATOR_ID,
+ urlGeneratorState: {
+ page: ILM_PAGES_POLICY_EDIT,
+ policyName: ilmPolicy?.name,
+ },
+ });
return (
<>
@@ -159,16 +163,10 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails })
/>
- {ilmPolicy && ilmPolicy.name ? (
-
- {ilmPolicy.name}
-
+ {ilmPolicy?.name && ilmPolicyLink ? (
+ {ilmPolicy!.name}
) : (
- i18nTexts.none
+ ilmPolicy?.name || i18nTexts.none
)}
>
diff --git a/x-pack/plugins/index_management/public/application/services/use_url_generator.ts b/x-pack/plugins/index_management/public/application/services/use_url_generator.ts
new file mode 100644
index 0000000000000..4e6bd05aa14b5
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/services/use_url_generator.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { useEffect, useState } from 'react';
+import {
+ UrlGeneratorContract,
+ UrlGeneratorId,
+ UrlGeneratorStateMapping,
+} from '../../../../../../src/plugins/share/public';
+import { useAppContext } from '../app_context';
+
+export const useUrlGenerator = ({
+ urlGeneratorId,
+ urlGeneratorState,
+}: {
+ urlGeneratorId: UrlGeneratorId;
+ urlGeneratorState: UrlGeneratorStateMapping[UrlGeneratorId]['State'];
+}) => {
+ const { urlGenerators } = useAppContext();
+ const [link, setLink] = useState();
+ useEffect(() => {
+ const updateLink = async (): Promise => {
+ let urlGenerator: UrlGeneratorContract;
+ try {
+ urlGenerator = urlGenerators.getUrlGenerator(urlGeneratorId);
+ const url = await urlGenerator.createUrl(urlGeneratorState);
+ setLink(url);
+ } catch (e) {
+ // do nothing
+ }
+ };
+
+ updateLink();
+ }, [urlGeneratorId, urlGeneratorState, urlGenerators]);
+ return link;
+};
diff --git a/x-pack/plugins/index_management/public/index.ts b/x-pack/plugins/index_management/public/index.ts
index 538dcaf25c47d..da6d90f22a384 100644
--- a/x-pack/plugins/index_management/public/index.ts
+++ b/x-pack/plugins/index_management/public/index.ts
@@ -4,14 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
import './index.scss';
-import { IndexMgmtUIPlugin, IndexManagementPluginSetup } from './plugin';
+import { IndexMgmtUIPlugin } from './plugin';
/** @public */
export const plugin = () => {
return new IndexMgmtUIPlugin();
};
-export { IndexManagementPluginSetup };
+export { IndexManagementPluginSetup } from './types';
export { getIndexListUri } from './application/services/routing';
diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts
index 6139ed5d2e6ad..855486528b797 100644
--- a/x-pack/plugins/index_management/public/plugin.ts
+++ b/x-pack/plugins/index_management/public/plugin.ts
@@ -6,10 +6,7 @@
import { i18n } from '@kbn/i18n';
import { CoreSetup } from '../../../../src/core/public';
-import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
-import { ManagementSetup } from '../../../../src/plugins/management/public';
-import { IngestManagerSetup } from '../../ingest_manager/public';
import { UIM_APP_NAME, PLUGIN } from '../common/constants';
import { httpService } from './application/services/http';
@@ -19,18 +16,13 @@ import { UiMetricService } from './application/services/ui_metric';
import { setExtensionsService } from './application/store/selectors';
import { setUiMetricService } from './application/services/api';
-import { IndexMgmtMetricsType } from './types';
-import { ExtensionsService, ExtensionsSetup } from './services';
-
-export interface IndexManagementPluginSetup {
- extensionsService: ExtensionsSetup;
-}
-
-interface PluginsDependencies {
- ingestManager?: IngestManagerSetup;
- usageCollection: UsageCollectionSetup;
- management: ManagementSetup;
-}
+import {
+ IndexManagementPluginSetup,
+ IndexMgmtMetricsType,
+ SetupDependencies,
+ StartDependencies,
+} from './types';
+import { ExtensionsService } from './services';
export class IndexMgmtUIPlugin {
private uiMetricService = new UiMetricService(UIM_APP_NAME);
@@ -43,7 +35,10 @@ export class IndexMgmtUIPlugin {
setUiMetricService(this.uiMetricService);
}
- public setup(coreSetup: CoreSetup, plugins: PluginsDependencies): IndexManagementPluginSetup {
+ public setup(
+ coreSetup: CoreSetup,
+ plugins: SetupDependencies
+ ): IndexManagementPluginSetup {
const { http, notifications } = coreSetup;
const { ingestManager, usageCollection, management } = plugins;
diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts
index 08fbf63450218..f860b89b0ba0c 100644
--- a/x-pack/plugins/index_management/public/types.ts
+++ b/x-pack/plugins/index_management/public/types.ts
@@ -4,4 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { ExtensionsSetup } from './services';
+import { IngestManagerSetup } from '../../ingest_manager/public';
+import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
+import { ManagementSetup } from '../../../../src/plugins/management/public';
+import { SharePluginStart } from '../../../../src/plugins/share/public';
+
export type IndexMgmtMetricsType = 'loaded' | 'click' | 'count';
+
+export interface IndexManagementPluginSetup {
+ extensionsService: ExtensionsSetup;
+}
+
+export interface SetupDependencies {
+ ingestManager?: IngestManagerSetup;
+ usageCollection: UsageCollectionSetup;
+ management: ManagementSetup;
+}
+
+export interface StartDependencies {
+ share: SharePluginStart;
+}
diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx
index 60a00371e5ade..54d3b783d22f6 100644
--- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx
+++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx
@@ -12,7 +12,7 @@ import { AlertsContextValue } from '../../../../../triggers_actions_ui/public/ap
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { InventoryMetricConditions } from '../../../../server/lib/alerting/inventory_metric_threshold/types';
import React from 'react';
-import { Expressions, AlertContextMeta, ExpressionRow } from './expression';
+import { Expressions, AlertContextMeta, ExpressionRow, defaultExpression } from './expression';
import { act } from 'react-dom/test-utils';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { Comparator } from '../../../../server/lib/alerting/metric_threshold/types';
@@ -105,6 +105,7 @@ describe('Expression', () => {
threshold: [],
timeSize: 1,
timeUnit: 'm',
+ customMetric: defaultExpression.customMetric,
},
]);
});
@@ -155,6 +156,7 @@ describe('ExpressionRow', () => {
alertsContextMetadata={{
customMetrics: [],
}}
+ fields={[{ name: 'some.system.field', type: 'bzzz' }]}
/>
);
diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx
index 66d547eb50d9c..097e0f1f1690b 100644
--- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx
+++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx
@@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { set } from '@elastic/safer-lodash-set';
-import { debounce, pick, uniqBy, isEqual } from 'lodash';
+import { debounce, pick } from 'lodash';
import { Unit } from '@elastic/datemath';
import React, { useCallback, useMemo, useEffect, useState, ChangeEvent } from 'react';
+import { IFieldType } from 'src/plugins/data/public';
import {
EuiFlexGroup,
EuiFlexItem,
@@ -23,7 +23,6 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
-import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label';
import { toMetricOpt } from '../../../../common/snapshot_metric_i18n';
import { AlertPreview } from '../../common';
import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics';
@@ -95,13 +94,18 @@ interface Props {
setAlertProperty(key: string, value: any): void;
}
-const defaultExpression = {
+export const defaultExpression = {
metric: 'cpu' as SnapshotMetricType,
comparator: Comparator.GT,
threshold: [],
timeSize: 1,
timeUnit: 'm',
- customMetric: undefined,
+ customMetric: {
+ type: 'custom',
+ id: 'alert-custom-metric',
+ field: '',
+ aggregation: 'avg',
+ },
} as InventoryMetricConditions;
export const Expressions: React.FC = (props) => {
@@ -226,7 +230,7 @@ export const Expressions: React.FC = (props) => {
metric: md.options.metric!.type,
customMetric: SnapshotCustomMetricInputRT.is(md.options.metric)
? md.options.metric
- : undefined,
+ : defaultExpression.customMetric,
} as InventoryMetricConditions,
]);
} else {
@@ -306,6 +310,7 @@ export const Expressions: React.FC = (props) => {
errors={errors[idx] || emptyError}
expression={e || {}}
alertsContextMetadata={alertsContext.metadata}
+ fields={derivedIndexPattern.fields}
/>
);
})}
@@ -415,6 +420,7 @@ interface ExpressionRowProps {
remove(id: number): void;
setAlertParams(id: number, params: Partial): void;
alertsContextMetadata: AlertsContextValue['metadata'];
+ fields: IFieldType[];
}
const StyledExpressionRow = euiStyled(EuiFlexGroup)`
@@ -428,48 +434,25 @@ const StyledExpression = euiStyled.div`
`;
export const ExpressionRow: React.FC = (props) => {
- const {
- setAlertParams,
- expression,
- errors,
- expressionId,
- remove,
- canDelete,
- alertsContextMetadata,
- } = props;
+ const { setAlertParams, expression, errors, expressionId, remove, canDelete, fields } = props;
const { metric, comparator = Comparator.GT, threshold = [], customMetric } = expression;
- const [customMetrics, updateCustomMetrics] = useState([]);
-
- // Create and uniquify a list of custom metrics including:
- // - The alert metadata context (which only gives us custom metrics on the inventory page)
- // - The custom metric stored in the expression (necessary when editing this alert without having
- // access to the metadata context)
- // - Whatever custom metrics were previously stored in this list (to preserve the custom metric in the dropdown
- // if the user edits the alert and switches away from the custom metric)
- useEffect(() => {
- const ctxCustomMetrics = alertsContextMetadata?.customMetrics ?? [];
- const expressionCustomMetrics = customMetric ? [customMetric] : [];
- const newCustomMetrics = uniqBy(
- [...customMetrics, ...ctxCustomMetrics, ...expressionCustomMetrics],
- (cm: SnapshotCustomMetricInput) => cm.id
- );
- if (!isEqual(customMetrics, newCustomMetrics)) updateCustomMetrics(newCustomMetrics);
- }, [alertsContextMetadata, customMetric, customMetrics, updateCustomMetrics]);
const updateMetric = useCallback(
(m?: SnapshotMetricType | string) => {
- const newMetric = SnapshotMetricTypeRT.is(m) ? m : 'custom';
+ const newMetric = SnapshotMetricTypeRT.is(m) ? m : Boolean(m) ? 'custom' : undefined;
const newAlertParams = { ...expression, metric: newMetric };
- if (newMetric === 'custom' && customMetrics) {
- set(
- newAlertParams,
- 'customMetric',
- customMetrics.find((cm) => cm.id === m)
- );
- }
setAlertParams(expressionId, newAlertParams);
},
- [expressionId, expression, setAlertParams, customMetrics]
+ [expressionId, expression, setAlertParams]
+ );
+
+ const updateCustomMetric = useCallback(
+ (cm?: SnapshotCustomMetricInput) => {
+ if (SnapshotCustomMetricInputRT.is(cm)) {
+ setAlertParams(expressionId, { ...expression, customMetric: cm });
+ }
+ },
+ [expressionId, expression, setAlertParams]
);
const updateComparator = useCallback(
@@ -515,17 +498,8 @@ export const ExpressionRow: React.FC = (props) => {
myMetrics = containerMetricTypes;
break;
}
- const baseMetricOpts = myMetrics.map(toMetricOpt);
- const customMetricOpts = customMetrics
- ? customMetrics.map((m, i) => ({
- text: getCustomMetricLabel(m),
- value: m.id,
- }))
- : [];
- return [...baseMetricOpts, ...customMetricOpts];
- }, [props.nodeType, customMetrics]);
-
- const selectedMetricValue = metric === 'custom' && customMetric ? customMetric.id : metric!;
+ return myMetrics.map(toMetricOpt);
+ }, [props.nodeType]);
return (
<>
@@ -535,8 +509,8 @@ export const ExpressionRow: React.FC = (props) => {
v?.value === selectedMetricValue)?.text || '',
+ value: metric!,
+ text: ofFields.find((v) => v?.value === metric)?.text || '',
}}
metrics={
ofFields.filter((m) => m !== undefined && m.value !== undefined) as Array<{
@@ -545,7 +519,10 @@ export const ExpressionRow: React.FC = (props) => {
}>
}
onChange={updateMetric}
+ onChangeCustom={updateCustomMetric}
errors={errors}
+ customMetric={customMetric}
+ fields={fields}
/>
diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx
index 5418eab3c5fc2..2dd2938dfd55a 100644
--- a/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx
+++ b/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useState } from 'react';
+import React, { useState, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { debounce } from 'lodash';
import {
EuiExpression,
EuiPopover,
@@ -14,16 +15,33 @@ import {
EuiFlexItem,
EuiFormRow,
EuiComboBox,
+ EuiButtonGroup,
+ EuiSpacer,
+ EuiSelect,
+ EuiText,
+ EuiFieldText,
} from '@elastic/eui';
+import { IFieldType } from 'src/plugins/data/public';
import { EuiPopoverTitle, EuiButtonIcon } from '@elastic/eui';
+import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { IErrorObject } from '../../../../../triggers_actions_ui/public/types';
+import {
+ SnapshotCustomMetricInput,
+ SnapshotCustomMetricInputRT,
+ SnapshotCustomAggregation,
+ SNAPSHOT_CUSTOM_AGGREGATIONS,
+ SnapshotCustomAggregationRT,
+} from '../../../../common/http_api/snapshot_api';
interface Props {
metric?: { value: string; text: string };
metrics: Array<{ value: string; text: string }>;
errors: IErrorObject;
onChange: (metric?: string) => void;
+ onChangeCustom: (customMetric?: SnapshotCustomMetricInput) => void;
+ customMetric?: SnapshotCustomMetricInput;
+ fields: IFieldType[];
popupPosition?:
| 'upCenter'
| 'upLeft'
@@ -39,8 +57,40 @@ interface Props {
| 'rightDown';
}
-export const MetricExpression = ({ metric, metrics, errors, onChange, popupPosition }: Props) => {
- const [aggFieldPopoverOpen, setAggFieldPopoverOpen] = useState(false);
+const AGGREGATION_LABELS = {
+ ['avg']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.avg', {
+ defaultMessage: 'Average',
+ }),
+ ['max']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.max', {
+ defaultMessage: 'Max',
+ }),
+ ['min']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.min', {
+ defaultMessage: 'Min',
+ }),
+ ['rate']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.rate', {
+ defaultMessage: 'Rate',
+ }),
+};
+const aggregationOptions = SNAPSHOT_CUSTOM_AGGREGATIONS.map((k) => ({
+ text: AGGREGATION_LABELS[k as SnapshotCustomAggregation],
+ value: k,
+}));
+
+export const MetricExpression = ({
+ metric,
+ metrics,
+ customMetric,
+ fields,
+ errors,
+ onChange,
+ onChangeCustom,
+ popupPosition,
+}: Props) => {
+ const [popoverOpen, setPopoverOpen] = useState(false);
+ const [customMetricTabOpen, setCustomMetricTabOpen] = useState(metric?.value === 'custom');
+ const [selectedOption, setSelectedOption] = useState(metric?.value);
+ const [fieldDisplayedCustomLabel, setFieldDisplayedCustomLabel] = useState(customMetric?.label);
+
const firstFieldOption = {
text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.metric.selectFieldLabel', {
defaultMessage: 'Select a metric',
@@ -48,13 +98,84 @@ export const MetricExpression = ({ metric, metrics, errors, onChange, popupPosit
value: '',
};
+ const fieldOptions = useMemo(
+ () =>
+ fields
+ .filter((f) => f.aggregatable && f.type === 'number' && !(customMetric?.field === f.name))
+ .map((f) => ({ label: f.name })),
+ [fields, customMetric?.field]
+ );
+
+ const expressionDisplayValue = useMemo(
+ () => {
+ return customMetricTabOpen
+ ? customMetric?.field && getCustomMetricLabel(customMetric)
+ : metric?.text || firstFieldOption.text;
+ },
+ // The ?s are confusing eslint here, so...
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [customMetricTabOpen, metric, customMetric, firstFieldOption]
+ );
+
+ const onChangeTab = useCallback(
+ (id) => {
+ if (id === 'metric-popover-custom') {
+ setCustomMetricTabOpen(true);
+ onChange('custom');
+ } else {
+ setCustomMetricTabOpen(false);
+ onChange(selectedOption);
+ }
+ },
+ [setCustomMetricTabOpen, onChange, selectedOption]
+ );
+
+ const onAggregationChange = useCallback(
+ (e) => {
+ const value = e.target.value;
+ const aggValue: SnapshotCustomAggregation = SnapshotCustomAggregationRT.is(value)
+ ? value
+ : 'avg';
+ const newCustomMetric = {
+ ...customMetric,
+ aggregation: aggValue,
+ };
+ if (SnapshotCustomMetricInputRT.is(newCustomMetric)) onChangeCustom(newCustomMetric);
+ },
+ [customMetric, onChangeCustom]
+ );
+
+ const onFieldChange = useCallback(
+ (selectedOptions: Array<{ label: string }>) => {
+ const newCustomMetric = {
+ ...customMetric,
+ field: selectedOptions[0].label,
+ };
+ if (SnapshotCustomMetricInputRT.is(newCustomMetric)) onChangeCustom(newCustomMetric);
+ },
+ [customMetric, onChangeCustom]
+ );
+
+ const debouncedOnChangeCustom = debounce(onChangeCustom, 500);
+ const onLabelChange = useCallback(
+ (e) => {
+ setFieldDisplayedCustomLabel(e.target.value);
+ const newCustomMetric = {
+ ...customMetric,
+ label: e.target.value,
+ };
+ if (SnapshotCustomMetricInputRT.is(newCustomMetric)) debouncedOnChangeCustom(newCustomMetric);
+ },
+ [customMetric, debouncedOnChangeCustom]
+ );
+
const availablefieldsOptions = metrics.map((m) => {
return { label: m.text, value: m.value };
}, []);
return (
0))}
+ value={expressionDisplayValue}
+ isActive={Boolean(popoverOpen || (errors.metric && errors.metric.length > 0))}
onClick={() => {
- setAggFieldPopoverOpen(true);
+ setPopoverOpen(true);
}}
color={errors.metric?.length ? 'danger' : 'secondary'}
/>
}
- isOpen={aggFieldPopoverOpen}
+ isOpen={popoverOpen}
closePopover={() => {
- setAggFieldPopoverOpen(false);
+ setPopoverOpen(false);
}}
anchorPosition={popupPosition ?? 'downRight'}
zIndex={8000}
>
-
-
setAggFieldPopoverOpen(false)}>
+
+ setPopoverOpen(false)}>
-
-
- 0} error={errors.metric}>
-
+
+ {customMetricTabOpen ? (
+ <>
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.infra.waffle.customMetrics.of', {
+ defaultMessage: 'of',
+ })}
+
+
+
+
+ 0}
+ />
+
+
+
+ of ".',
+ })}
+ >
+ 0}
- placeholder={firstFieldOption.text}
- options={availablefieldsOptions}
- noSuggestions={!availablefieldsOptions.length}
- selectedOptions={
- metric ? availablefieldsOptions.filter((a) => a.value === metric.value) : []
- }
- renderOption={(o: any) => o.label}
- onChange={(selectedOptions) => {
- if (selectedOptions.length > 0) {
- onChange(selectedOptions[0].value);
- setAggFieldPopoverOpen(false);
- } else {
- onChange();
- }
- }}
+ onChange={onLabelChange}
/>
-
-
+ >
+ ) : (
+
+
+
+ 0}
+ placeholder={firstFieldOption.text}
+ options={availablefieldsOptions}
+ noSuggestions={!availablefieldsOptions.length}
+ selectedOptions={
+ metric ? availablefieldsOptions.filter((a) => a.value === metric.value) : []
+ }
+ renderOption={(o: any) => o.label}
+ onChange={(selectedOptions) => {
+ if (selectedOptions.length > 0) {
+ onChange(selectedOptions[0].value);
+ setSelectedOption(selectedOptions[0].value);
+ } else {
+ onChange();
+ }
+ }}
+ />
+
+
+
+ )}
);
diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx
index 47ecd3c527fad..4b522d7d97730 100644
--- a/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx
+++ b/x-pack/plugins/infra/public/alerting/inventory/components/validation.tsx
@@ -6,14 +6,14 @@
import { i18n } from '@kbn/i18n';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { MetricExpressionParams } from '../../../../server/lib/alerting/metric_threshold/types';
+import { InventoryMetricConditions } from '../../../../server/lib/alerting/inventory_metric_threshold/types';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { ValidationResult } from '../../../../../triggers_actions_ui/public/types';
export function validateMetricThreshold({
criteria,
}: {
- criteria: MetricExpressionParams[];
+ criteria: InventoryMetricConditions[];
}): ValidationResult {
const validationResult = { errors: {} };
const errors: {
@@ -81,14 +81,20 @@ export function validateMetricThreshold({
})
);
}
-
- if (!c.metric && c.aggType !== 'count') {
+ if (!c.metric) {
errors[id].metric.push(
i18n.translate('xpack.infra.metrics.alertFlyout.error.metricRequired', {
defaultMessage: 'Metric is required.',
})
);
}
+ if (c.metric === 'custom' && !c.customMetric?.field) {
+ errors[id].metric.push(
+ i18n.translate('xpack.infra.metrics.alertFlyout.error.customMetricFieldRequired', {
+ defaultMessage: 'Field is required.',
+ })
+ );
+ }
});
return validationResult;
diff --git a/x-pack/plugins/infra/public/alerting/inventory/index.ts b/x-pack/plugins/infra/public/alerting/inventory/index.ts
index b49465db05135..d7afd73c0e3a7 100644
--- a/x-pack/plugins/infra/public/alerting/inventory/index.ts
+++ b/x-pack/plugins/infra/public/alerting/inventory/index.ts
@@ -21,6 +21,9 @@ export function createInventoryMetricAlertType(): AlertTypeModel {
defaultMessage: 'Alert when the inventory exceeds a defined threshold.',
}),
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/observability/${docLinks.DOC_LINK_VERSION}/infrastructure-threshold-alert.html`;
+ },
alertParamsExpression: React.lazy(() => import('./components/expression')),
validate: validateMetricThreshold,
defaultActionMessage: i18n.translate(
diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts
index 2e4cb2a53b6b5..60c22c42c00b6 100644
--- a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts
@@ -19,6 +19,9 @@ export function getAlertType(): AlertTypeModel {
defaultMessage: 'Alert when the log aggregation exceeds the threshold.',
}),
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/observability/${docLinks.DOC_LINK_VERSION}/logs-threshold-alert.html`;
+ },
alertParamsExpression: React.lazy(() => import('./components/expression_editor/editor')),
validate: validateExpression,
defaultActionMessage: i18n.translate(
diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts
index a48837792a3cc..05c69e5ccb78b 100644
--- a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts
+++ b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts
@@ -21,6 +21,9 @@ export function createMetricThresholdAlertType(): AlertTypeModel {
defaultMessage: 'Alert when the metrics aggregation exceeds the threshold.',
}),
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/observability/${docLinks.DOC_LINK_VERSION}/metrics-threshold-alert.html`;
+ },
alertParamsExpression: React.lazy(() => import('./components/expression')),
validate: validateMetricThreshold,
defaultActionMessage: i18n.translate(
diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts
index c57b9de0b4a9e..358438d11b6bd 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { useEffect, useState, useReducer, useCallback } from 'react';
+import { useMountedState } from 'react-use';
import createContainer from 'constate';
import { pick, throttle } from 'lodash';
import { TimeKey, timeKeyIsBetween } from '../../../../common/time';
@@ -146,15 +147,20 @@ const useFetchEntriesEffect = (
props: LogEntriesProps
) => {
const { services } = useKibanaContextForPlugin();
+ const isMounted = useMountedState();
const [prevParams, cachePrevParams] = useState();
const [startedStreaming, setStartedStreaming] = useState(false);
+ const dispatchIfMounted = useCallback((action) => (isMounted() ? dispatch(action) : undefined), [
+ dispatch,
+ isMounted,
+ ]);
const runFetchNewEntriesRequest = async (overrides: Partial = {}) => {
if (!props.startTimestamp || !props.endTimestamp) {
return;
}
- dispatch({ type: Action.FetchingNewEntries });
+ dispatchIfMounted({ type: Action.FetchingNewEntries });
try {
const commonFetchArgs: LogEntriesBaseRequest = {
@@ -175,13 +181,15 @@ const useFetchEntriesEffect = (
};
const { data: payload } = await fetchLogEntries(fetchArgs, services.http.fetch);
- dispatch({ type: Action.ReceiveNewEntries, payload });
+ dispatchIfMounted({ type: Action.ReceiveNewEntries, payload });
// Move position to the bottom if it's the first load.
// Do it in the next tick to allow the `dispatch` to fire
if (!props.timeKey && payload.bottomCursor) {
setTimeout(() => {
- props.jumpToTargetPosition(payload.bottomCursor!);
+ if (isMounted()) {
+ props.jumpToTargetPosition(payload.bottomCursor!);
+ }
});
} else if (
props.timeKey &&
@@ -192,7 +200,7 @@ const useFetchEntriesEffect = (
props.jumpToTargetPosition(payload.topCursor);
}
} catch (e) {
- dispatch({ type: Action.ErrorOnNewEntries });
+ dispatchIfMounted({ type: Action.ErrorOnNewEntries });
}
};
@@ -210,7 +218,7 @@ const useFetchEntriesEffect = (
return;
}
- dispatch({ type: Action.FetchingMoreEntries });
+ dispatchIfMounted({ type: Action.FetchingMoreEntries });
try {
const commonFetchArgs: LogEntriesBaseRequest = {
@@ -232,14 +240,14 @@ const useFetchEntriesEffect = (
const { data: payload } = await fetchLogEntries(fetchArgs, services.http.fetch);
- dispatch({
+ dispatchIfMounted({
type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter,
payload,
});
return payload.bottomCursor;
} catch (e) {
- dispatch({ type: Action.ErrorOnMoreEntries });
+ dispatchIfMounted({ type: Action.ErrorOnMoreEntries });
}
};
@@ -322,7 +330,7 @@ const useFetchEntriesEffect = (
after: props.endTimestamp > prevParams.endTimestamp,
};
- dispatch({ type: Action.ExpandRange, payload: shouldExpand });
+ dispatchIfMounted({ type: Action.ExpandRange, payload: shouldExpand });
};
const expandRangeEffectDependencies = [
diff --git a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts
index 9951b62fa64a3..42518127f68bf 100644
--- a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts
+++ b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts
@@ -6,13 +6,15 @@
/* eslint-disable max-classes-per-file */
-import { DependencyList, useEffect, useMemo, useRef, useState } from 'react';
+import { DependencyList, useEffect, useMemo, useRef, useState, useCallback } from 'react';
+import { useMountedState } from 'react-use';
interface UseTrackedPromiseArgs {
createPromise: (...args: Arguments) => Promise;
onResolve?: (result: Result) => void;
onReject?: (value: unknown) => void;
cancelPreviousOn?: 'creation' | 'settlement' | 'resolution' | 'rejection' | 'never';
+ triggerOrThrow?: 'always' | 'whenMounted';
}
/**
@@ -64,6 +66,16 @@ interface UseTrackedPromiseArgs {
* The last argument is a normal React hook dependency list that indicates
* under which conditions a new reference to the configuration object should be
* used.
+ *
+ * The `onResolve`, `onReject` and possible uncatched errors are only triggered
+ * if the underlying component is mounted. To ensure they always trigger (i.e.
+ * if the promise is called in a `useLayoutEffect`) use the `triggerOrThrow`
+ * attribute:
+ *
+ * 'whenMounted': (default) they are called only if the component is mounted.
+ *
+ * 'always': they always call. The consumer is then responsible of ensuring no
+ * side effects happen if the underlying component is not mounted.
*/
export const useTrackedPromise = (
{
@@ -71,9 +83,20 @@ export const useTrackedPromise = (
onResolve = noOp,
onReject = noOp,
cancelPreviousOn = 'never',
+ triggerOrThrow = 'whenMounted',
}: UseTrackedPromiseArgs,
dependencies: DependencyList
) => {
+ const isComponentMounted = useMountedState();
+ const shouldTriggerOrThrow = useCallback(() => {
+ switch (triggerOrThrow) {
+ case 'always':
+ return true;
+ case 'whenMounted':
+ return isComponentMounted();
+ }
+ }, [isComponentMounted, triggerOrThrow]);
+
/**
* If a promise is currently pending, this holds a reference to it and its
* cancellation function.
@@ -144,7 +167,7 @@ export const useTrackedPromise = (
(pendingPromise) => pendingPromise.promise !== newPendingPromise.promise
);
- if (onResolve) {
+ if (onResolve && shouldTriggerOrThrow()) {
onResolve(value);
}
@@ -173,11 +196,13 @@ export const useTrackedPromise = (
(pendingPromise) => pendingPromise.promise !== newPendingPromise.promise
);
- if (onReject) {
- onReject(value);
- }
+ if (shouldTriggerOrThrow()) {
+ if (onReject) {
+ onReject(value);
+ }
- throw value;
+ throw value;
+ }
}
),
};
diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts
index 91396bce359b0..e81207300a5f3 100644
--- a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts
+++ b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts
@@ -84,16 +84,14 @@ describe('Ingest Manager - packageToPackagePolicy', () => {
{
type: 'foo',
enabled: true,
- streams: [
- { id: 'foo-foo', enabled: true, data_stream: { dataset: 'foo', type: 'logs' } },
- ],
+ streams: [{ enabled: true, data_stream: { dataset: 'foo', type: 'logs' } }],
},
{
type: 'bar',
enabled: true,
streams: [
- { id: 'bar-bar', enabled: true, data_stream: { dataset: 'bar', type: 'logs' } },
- { id: 'bar-bar2', enabled: true, data_stream: { dataset: 'bar2', type: 'logs' } },
+ { enabled: true, data_stream: { dataset: 'bar', type: 'logs' } },
+ { enabled: true, data_stream: { dataset: 'bar2', type: 'logs' } },
],
},
]);
@@ -142,7 +140,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => {
enabled: true,
streams: [
{
- id: 'foo-foo',
enabled: true,
data_stream: { dataset: 'foo', type: 'logs' },
vars: { 'var-name': { value: 'foo-var-value' } },
@@ -154,13 +151,11 @@ describe('Ingest Manager - packageToPackagePolicy', () => {
enabled: true,
streams: [
{
- id: 'bar-bar',
enabled: true,
data_stream: { dataset: 'bar', type: 'logs' },
vars: { 'var-name': { type: 'text', value: 'bar-var-value' } },
},
{
- id: 'bar-bar2',
enabled: true,
data_stream: { dataset: 'bar2', type: 'logs' },
vars: { 'var-name': { type: 'yaml', value: 'bar2-var-value' } },
@@ -258,7 +253,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => {
},
streams: [
{
- id: 'foo-foo',
enabled: true,
data_stream: { dataset: 'foo', type: 'logs' },
vars: {
@@ -276,7 +270,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => {
},
streams: [
{
- id: 'bar-bar',
enabled: true,
data_stream: { dataset: 'bar', type: 'logs' },
vars: {
@@ -284,7 +277,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => {
},
},
{
- id: 'bar-bar2',
enabled: true,
data_stream: { dataset: 'bar2', type: 'logs' },
vars: {
@@ -298,7 +290,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => {
enabled: false,
streams: [
{
- id: 'with-disabled-streams-disabled',
enabled: false,
data_stream: { dataset: 'disabled', type: 'logs' },
vars: {
@@ -306,7 +297,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => {
},
},
{
- id: 'with-disabled-streams-disabled2',
enabled: false,
data_stream: { dataset: 'disabled2', type: 'logs' },
},
diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts
index 822747916ebc5..cbdfa25ed7f7e 100644
--- a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts
+++ b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts
@@ -8,11 +8,10 @@ import {
RegistryPolicyTemplate,
RegistryVarsEntry,
RegistryStream,
- PackagePolicy,
PackagePolicyConfigRecord,
PackagePolicyConfigRecordEntry,
- PackagePolicyInput,
- PackagePolicyInputStream,
+ NewPackagePolicyInput,
+ NewPackagePolicyInputStream,
NewPackagePolicy,
} from '../types';
@@ -42,8 +41,10 @@ const getStreamsForInputType = (
/*
* This service creates a package policy inputs definition from defaults provided in package info
*/
-export const packageToPackagePolicyInputs = (packageInfo: PackageInfo): PackagePolicy['inputs'] => {
- const inputs: PackagePolicy['inputs'] = [];
+export const packageToPackagePolicyInputs = (
+ packageInfo: PackageInfo
+): NewPackagePolicy['inputs'] => {
+ const inputs: NewPackagePolicy['inputs'] = [];
// Assume package will only ever ship one package policy template for now
const packagePolicyTemplate: RegistryPolicyTemplate | null =
@@ -71,12 +72,11 @@ export const packageToPackagePolicyInputs = (packageInfo: PackageInfo): PackageP
};
// Map each package input stream into package policy input stream
- const streams: PackagePolicyInputStream[] = getStreamsForInputType(
+ const streams: NewPackagePolicyInputStream[] = getStreamsForInputType(
packageInput.type,
packageInfo
).map((packageStream) => {
- const stream: PackagePolicyInputStream = {
- id: `${packageInput.type}-${packageStream.data_stream.dataset}`,
+ const stream: NewPackagePolicyInputStream = {
enabled: packageStream.enabled === false ? false : true,
data_stream: packageStream.data_stream,
};
@@ -86,7 +86,7 @@ export const packageToPackagePolicyInputs = (packageInfo: PackageInfo): PackageP
return stream;
});
- const input: PackagePolicyInput = {
+ const input: NewPackagePolicyInput = {
type: packageInput.type,
enabled: streams.length ? !!streams.find((stream) => stream.enabled) : true,
streams,
diff --git a/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts b/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts
index 724dbae5dac85..ae16899a4b6f9 100644
--- a/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts
+++ b/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts
@@ -18,7 +18,6 @@ export interface PackagePolicyConfigRecordEntry {
export type PackagePolicyConfigRecord = Record;
export interface NewPackagePolicyInputStream {
- id: string;
enabled: boolean;
data_stream: {
dataset: string;
@@ -29,6 +28,7 @@ export interface NewPackagePolicyInputStream {
}
export interface PackagePolicyInputStream extends NewPackagePolicyInputStream {
+ id: string;
compiled_stream?: any;
}
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx
index 175bfb1469902..177354dad14dc 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx
@@ -14,7 +14,7 @@ import {
EuiSpacer,
EuiButtonEmpty,
} from '@elastic/eui';
-import { PackagePolicyInput, RegistryVarsEntry } from '../../../../types';
+import { NewPackagePolicyInput, RegistryVarsEntry } from '../../../../types';
import {
isAdvancedVar,
PackagePolicyConfigValidationResults,
@@ -28,8 +28,8 @@ const FlexItemWithMaxWidth = styled(EuiFlexItem)`
export const PackagePolicyInputConfig: React.FunctionComponent<{
packageInputVars?: RegistryVarsEntry[];
- packagePolicyInput: PackagePolicyInput;
- updatePackagePolicyInput: (updatedInput: Partial) => void;
+ packagePolicyInput: NewPackagePolicyInput;
+ updatePackagePolicyInput: (updatedInput: Partial) => void;
inputVarsValidationResults: PackagePolicyConfigValidationResults;
forceShowErrors?: boolean;
}> = memo(
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx
index 1e43cc0d5938e..79ff0cc29850c 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx
@@ -17,7 +17,7 @@ import {
EuiSpacer,
} from '@elastic/eui';
import {
- PackagePolicyInput,
+ NewPackagePolicyInput,
PackagePolicyInputStream,
RegistryInput,
RegistryStream,
@@ -40,7 +40,7 @@ const ShortenedHorizontalRule = styled(EuiHorizontalRule)`
const shouldShowStreamsByDefault = (
packageInput: RegistryInput,
packageInputStreams: Array,
- packagePolicyInput: PackagePolicyInput
+ packagePolicyInput: NewPackagePolicyInput
): boolean => {
return (
packagePolicyInput.enabled &&
@@ -63,8 +63,8 @@ const shouldShowStreamsByDefault = (
export const PackagePolicyInputPanel: React.FunctionComponent<{
packageInput: RegistryInput;
packageInputStreams: Array;
- packagePolicyInput: PackagePolicyInput;
- updatePackagePolicyInput: (updatedInput: Partial) => void;
+ packagePolicyInput: NewPackagePolicyInput;
+ updatePackagePolicyInput: (updatedInput: Partial) => void;
inputValidationResults: PackagePolicyInputValidationResults;
forceShowErrors?: boolean;
}> = memo(
@@ -210,7 +210,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
...updatedStream,
};
- const updatedInput: Partial = {
+ const updatedInput: Partial = {
streams: newStreams,
};
@@ -227,7 +227,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{
updatePackagePolicyInput(updatedInput);
}}
inputStreamValidationResults={
- inputValidationResults.streams![packagePolicyInputStream!.id]
+ inputValidationResults.streams![packagePolicyInputStream!.data_stream!.dataset]
}
forceShowErrors={forceShowErrors}
/>
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx
index 3d33edd468151..963d0da50ce7f 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx
@@ -16,7 +16,7 @@ import {
EuiSpacer,
EuiButtonEmpty,
} from '@elastic/eui';
-import { PackagePolicyInputStream, RegistryStream, RegistryVarsEntry } from '../../../../types';
+import { NewPackagePolicyInputStream, RegistryStream, RegistryVarsEntry } from '../../../../types';
import {
isAdvancedVar,
PackagePolicyConfigValidationResults,
@@ -30,8 +30,8 @@ const FlexItemWithMaxWidth = styled(EuiFlexItem)`
export const PackagePolicyInputStreamConfig: React.FunctionComponent<{
packageInputStream: RegistryStream;
- packagePolicyInputStream: PackagePolicyInputStream;
- updatePackagePolicyInputStream: (updatedStream: Partial) => void;
+ packagePolicyInputStream: NewPackagePolicyInputStream;
+ updatePackagePolicyInputStream: (updatedStream: Partial) => void;
inputStreamValidationResults: PackagePolicyConfigValidationResults;
forceShowErrors?: boolean;
}> = memo(
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test..ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts
similarity index 91%
rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test..ts
rename to x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts
index 9022e312ece79..8d46fed1ff14e 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test..ts
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts
@@ -154,7 +154,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
},
streams: [
{
- id: 'foo-foo',
data_stream: { dataset: 'foo', type: 'logs' },
enabled: true,
vars: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } },
@@ -170,13 +169,11 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
},
streams: [
{
- id: 'bar-bar',
data_stream: { dataset: 'bar', type: 'logs' },
enabled: true,
vars: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } },
},
{
- id: 'bar-bar2',
data_stream: { dataset: 'bar2', type: 'logs' },
enabled: true,
vars: { 'var-name': { value: undefined, type: 'text' } },
@@ -193,13 +190,11 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
enabled: true,
streams: [
{
- id: 'with-disabled-streams-disabled',
data_stream: { dataset: 'disabled', type: 'logs' },
enabled: false,
vars: { 'var-name': { value: undefined, type: 'text' } },
},
{
- id: 'with-disabled-streams-disabled-without-vars',
data_stream: { dataset: 'disabled2', type: 'logs' },
enabled: false,
},
@@ -213,8 +208,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
},
streams: [
{
- id: 'with-no-stream-vars-bar',
- data_stream: { dataset: 'bar', type: 'logs' },
+ data_stream: { dataset: 'with-no-stream-vars-bar', type: 'logs' },
enabled: true,
},
],
@@ -236,7 +230,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
},
streams: [
{
- id: 'foo-foo',
data_stream: { dataset: 'foo', type: 'logs' },
enabled: true,
vars: { 'var-name': { value: 'invalidyaml: test\n foo bar:', type: 'yaml' } },
@@ -252,13 +245,11 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
},
streams: [
{
- id: 'bar-bar',
data_stream: { dataset: 'bar', type: 'logs' },
enabled: true,
vars: { 'var-name': { value: ' \n\n', type: 'yaml' } },
},
{
- id: 'bar-bar2',
data_stream: { dataset: 'bar2', type: 'logs' },
enabled: true,
vars: { 'var-name': { value: undefined, type: 'text' } },
@@ -275,7 +266,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
enabled: true,
streams: [
{
- id: 'with-disabled-streams-disabled',
data_stream: { dataset: 'disabled', type: 'logs' },
enabled: false,
vars: {
@@ -286,7 +276,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
},
},
{
- id: 'with-disabled-streams-disabled-without-vars',
data_stream: { dataset: 'disabled2', type: 'logs' },
enabled: false,
},
@@ -300,8 +289,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
},
streams: [
{
- id: 'with-no-stream-vars-bar',
- data_stream: { dataset: 'bar', type: 'logs' },
+ data_stream: { dataset: 'with-no-stream-vars-bar', type: 'logs' },
enabled: true,
},
],
@@ -320,21 +308,21 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
'foo-input2-var-name': null,
'foo-input3-var-name': null,
},
- streams: { 'foo-foo': { vars: { 'var-name': null } } },
+ streams: { foo: { vars: { 'var-name': null } } },
},
bar: {
vars: { 'bar-input-var-name': null, 'bar-input2-var-name': null },
streams: {
- 'bar-bar': { vars: { 'var-name': null } },
- 'bar-bar2': { vars: { 'var-name': null } },
+ bar: { vars: { 'var-name': null } },
+ bar2: { vars: { 'var-name': null } },
},
},
'with-disabled-streams': {
streams: {
- 'with-disabled-streams-disabled': {
+ disabled: {
vars: { 'var-name': null },
},
- 'with-disabled-streams-disabled-without-vars': {},
+ disabled2: {},
},
},
'with-no-stream-vars': {
@@ -364,7 +352,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
'foo-input2-var-name': ['foo-input2-var-name is required'],
'foo-input3-var-name': ['foo-input3-var-name is required'],
},
- streams: { 'foo-foo': { vars: { 'var-name': ['Invalid YAML format'] } } },
+ streams: { foo: { vars: { 'var-name': ['Invalid YAML format'] } } },
},
bar: {
vars: {
@@ -372,14 +360,14 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
'bar-input2-var-name': ['bar-input2-var-name is required'],
},
streams: {
- 'bar-bar': { vars: { 'var-name': ['var-name is required'] } },
- 'bar-bar2': { vars: { 'var-name': null } },
+ bar: { vars: { 'var-name': ['var-name is required'] } },
+ bar2: { vars: { 'var-name': null } },
},
},
'with-disabled-streams': {
streams: {
- 'with-disabled-streams-disabled': { vars: { 'var-name': null } },
- 'with-disabled-streams-disabled-without-vars': {},
+ disabled: { vars: { 'var-name': null } },
+ disabled2: {},
},
},
'with-no-stream-vars': {
@@ -427,7 +415,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
'foo-input2-var-name': ['foo-input2-var-name is required'],
'foo-input3-var-name': ['foo-input3-var-name is required'],
},
- streams: { 'foo-foo': { vars: { 'var-name': null } } },
+ streams: { foo: { vars: { 'var-name': null } } },
},
bar: {
vars: {
@@ -435,16 +423,16 @@ describe('Ingest Manager - validatePackagePolicy()', () => {
'bar-input2-var-name': ['bar-input2-var-name is required'],
},
streams: {
- 'bar-bar': { vars: { 'var-name': null } },
- 'bar-bar2': { vars: { 'var-name': null } },
+ bar: { vars: { 'var-name': null } },
+ bar2: { vars: { 'var-name': null } },
},
},
'with-disabled-streams': {
streams: {
- 'with-disabled-streams-disabled': {
+ disabled: {
vars: { 'var-name': null },
},
- 'with-disabled-streams-disabled-without-vars': {},
+ disabled2: {},
},
},
'with-no-stream-vars': {
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts
index 9ce73c0690ccb..1126cd7e58e18 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts
@@ -151,7 +151,7 @@ export const validatePackagePolicy = (
);
}
- inputValidationResults.streams![stream.id] = streamValidationResults;
+ inputValidationResults.streams![stream.data_stream.dataset] = streamValidationResults;
});
} else {
delete inputValidationResults.streams;
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx
index d3d5e60c34e58..b335ff439684b 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx
@@ -5,7 +5,12 @@
*/
import React from 'react';
import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { PackageInfo, RegistryStream, NewPackagePolicy, PackagePolicyInput } from '../../../types';
+import {
+ PackageInfo,
+ RegistryStream,
+ NewPackagePolicy,
+ NewPackagePolicyInput,
+} from '../../../types';
import { Loading } from '../../../components';
import { PackagePolicyValidationResults } from './services';
import { PackagePolicyInputPanel, CustomPackagePolicy } from './components';
@@ -71,7 +76,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{
packageInput={packageInput}
packageInputStreams={packageInputStreams}
packagePolicyInput={packagePolicyInput}
- updatePackagePolicyInput={(updatedInput: Partial) => {
+ updatePackagePolicyInput={(updatedInput: Partial) => {
const indexOfUpdatedInput = packagePolicy.inputs.findIndex(
(input) => input.type === packageInput.type
);
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts
index 386ffa5649cc2..1cf8077aeda40 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts
@@ -17,7 +17,9 @@ export {
NewPackagePolicy,
UpdatePackagePolicy,
PackagePolicyInput,
+ NewPackagePolicyInput,
PackagePolicyInputStream,
+ NewPackagePolicyInputStream,
PackagePolicyConfigRecord,
PackagePolicyConfigRecordEntry,
Output,
diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts
index 198a54ca84125..1d221b8b1eead 100644
--- a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts
@@ -35,8 +35,7 @@ import {
getPackageInfo,
handleInstallPackageFailure,
isBulkInstallError,
- installPackageFromRegistry,
- installPackageByUpload,
+ installPackage,
removeInstallation,
getLimitedPackages,
getInstallationObject,
@@ -149,7 +148,8 @@ export const installPackageFromRegistryHandler: RequestHandler<
const { pkgName, pkgVersion } = splitPkgKey(pkgkey);
const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName });
try {
- const res = await installPackageFromRegistry({
+ const res = await installPackage({
+ installSource: 'registry',
savedObjectsClient,
pkgkey,
callCluster,
@@ -224,7 +224,8 @@ export const installPackageByUploadHandler: RequestHandler<
const contentType = request.headers['content-type'] as string; // from types it could also be string[] or undefined but this is checked later
const archiveBuffer = Buffer.from(request.body);
try {
- const res = await installPackageByUpload({
+ const res = await installPackage({
+ installSource: 'upload',
savedObjectsClient,
callCluster,
archiveBuffer,
diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts
index 44c2ccda3bd2a..f47b8499a1b69 100644
--- a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts
@@ -28,6 +28,13 @@ jest.mock('../../services/package_policy', (): {
create: jest.fn((soClient, callCluster, newData) =>
Promise.resolve({
...newData,
+ inputs: newData.inputs.map((input) => ({
+ ...input,
+ streams: input.streams.map((stream) => ({
+ id: stream.data_stream.dataset,
+ ...stream,
+ })),
+ })),
id: '1',
revision: 1,
updated_at: new Date().toISOString(),
diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts
index d9baeca4deb47..3a2b9ba7a744f 100644
--- a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts
@@ -7,7 +7,6 @@ import { TypeOf } from '@kbn/config-schema';
import Boom from '@hapi/boom';
import { RequestHandler, SavedObjectsErrorHelpers } from '../../../../../../src/core/server';
import { appContextService, packagePolicyService } from '../../services';
-import { getPackageInfo } from '../../services/epm/packages';
import {
GetPackagePoliciesRequestSchema,
GetOnePackagePolicyRequestSchema,
@@ -134,21 +133,11 @@ export const updatePackagePolicyHandler: RequestHandler<
const newData = { ...request.body };
const pkg = newData.package || packagePolicy.package;
const inputs = newData.inputs || packagePolicy.inputs;
- if (pkg && (newData.inputs || newData.package)) {
- const pkgInfo = await getPackageInfo({
- savedObjectsClient: soClient,
- pkgName: pkg.name,
- pkgVersion: pkg.version,
- });
- newData.inputs = (await packagePolicyService.assignPackageStream(pkgInfo, inputs)) as TypeOf<
- typeof CreatePackagePolicyRequestSchema.body
- >['inputs'];
- }
const updatedPackagePolicy = await packagePolicyService.update(
soClient,
request.params.packagePolicyId,
- newData,
+ { ...newData, package: pkg, inputs },
{ user }
);
return response.ok({
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts
similarity index 95%
rename from x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts
rename to x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts
index 695db9db73fa2..102324c18bd43 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { pkgToPkgKey } from './index';
+import { pkgToPkgKey } from '../registry/index';
const cache: Map = new Map();
export const cacheGet = (key: string) => cache.get(key);
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts
index 91ed489b3a5bb..27451ed6b5e60 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts
@@ -4,21 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import yaml from 'js-yaml';
-import { uniq } from 'lodash';
-
-import {
- ArchivePackage,
- RegistryPolicyTemplate,
- RegistryDataStream,
- RegistryInput,
- RegistryStream,
- RegistryVarsEntry,
-} from '../../../../common/types';
+import { ArchivePackage } from '../../../../common/types';
import { PackageInvalidArchiveError, PackageUnsupportedMediaTypeError } from '../../../errors';
-import { pkgToPkgKey } from '../registry';
-import { cacheGet, cacheSet, setArchiveFilelist } from '../registry/cache';
-import { unzipBuffer, untarBuffer, ArchiveEntry } from '../registry/extract';
+import {
+ cacheSet,
+ cacheDelete,
+ getArchiveFilelist,
+ setArchiveFilelist,
+ deleteArchiveFilelist,
+} from './cache';
+import { ArchiveEntry, getBufferExtractor } from '../registry/extract';
+import { parseAndVerifyArchive } from './validation';
+
+export * from './cache';
export async function loadArchivePackage({
archiveBuffer,
@@ -37,30 +35,23 @@ export async function loadArchivePackage({
};
}
-function getBufferExtractorForContentType(contentType: string) {
- if (contentType === 'application/gzip') {
- return untarBuffer;
- } else if (contentType === 'application/zip') {
- return unzipBuffer;
- } else {
- throw new PackageUnsupportedMediaTypeError(
- `Unsupported media type ${contentType}. Please use 'application/gzip' or 'application/zip'`
- );
- }
-}
-
export async function unpackArchiveToCache(
archiveBuffer: Buffer,
contentType: string,
filter = (entry: ArchiveEntry): boolean => true
): Promise {
- const bufferExtractor = getBufferExtractorForContentType(contentType);
+ const bufferExtractor = getBufferExtractor({ contentType });
+ if (!bufferExtractor) {
+ throw new PackageUnsupportedMediaTypeError(
+ `Unsupported media type ${contentType}. Please use 'application/gzip' or 'application/zip'`
+ );
+ }
const paths: string[] = [];
try {
await bufferExtractor(archiveBuffer, filter, (entry: ArchiveEntry) => {
const { path, buffer } = entry;
// skip directories
- if (path.slice(-1) === '/') return;
+ if (path.endsWith('/')) return;
if (buffer) {
cacheSet(path, buffer);
paths.push(path);
@@ -68,7 +59,7 @@ export async function unpackArchiveToCache(
});
} catch (error) {
throw new PackageInvalidArchiveError(
- `Error during extraction of uploaded package: ${error}. Assumed content type was ${contentType}, check if this matches the archive type.`
+ `Error during extraction of package: ${error}. Assumed content type was ${contentType}, check if this matches the archive type.`
);
}
@@ -76,257 +67,20 @@ export async function unpackArchiveToCache(
// unpacking a zip file with untarBuffer() just results in nothing.
if (paths.length === 0) {
throw new PackageInvalidArchiveError(
- `Uploaded archive seems empty. Assumed content type was ${contentType}, check if this matches the archive type.`
+ `Archive seems empty. Assumed content type was ${contentType}, check if this matches the archive type.`
);
}
return paths;
}
-// TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the
-// package registry. At some point this should probably be replaced (or enhanced) with verification based on
-// https://github.com/elastic/package-spec/
-
-function parseAndVerifyArchive(paths: string[]): ArchivePackage {
- // The top-level directory must match pkgName-pkgVersion, and no other top-level files or directories may be present
- const toplevelDir = paths[0].split('/')[0];
- paths.forEach((path) => {
- if (path.split('/')[0] !== toplevelDir) {
- throw new PackageInvalidArchiveError('Package contains more than one top-level directory.');
- }
- });
-
- // The package must contain a manifest file ...
- const manifestFile = `${toplevelDir}/manifest.yml`;
- const manifestBuffer = cacheGet(manifestFile);
- if (!paths.includes(manifestFile) || !manifestBuffer) {
- throw new PackageInvalidArchiveError('Package must contain a top-level manifest.yml file.');
- }
-
- // ... which must be valid YAML
- let manifest;
- try {
- manifest = yaml.load(manifestBuffer.toString());
- } catch (error) {
- throw new PackageInvalidArchiveError(`Could not parse top-level package manifest: ${error}.`);
- }
-
- // Package name and version from the manifest must match those from the toplevel directory
- const pkgKey = pkgToPkgKey({ name: manifest.name, version: manifest.version });
- if (toplevelDir !== pkgKey) {
- throw new PackageInvalidArchiveError(
- `Name ${manifest.name} and version ${manifest.version} do not match top-level directory ${toplevelDir}`
- );
- }
-
- const { name, version, description, type, categories, format_version: formatVersion } = manifest;
- // check for mandatory fields
- if (!(name && version && description && type && categories && formatVersion)) {
- throw new PackageInvalidArchiveError(
- 'Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version'
- );
- }
-
- const dataStreams = parseAndVerifyDataStreams(paths, name, version);
- const policyTemplates = parseAndVerifyPolicyTemplates(manifest);
-
- return {
- name,
- version,
- description,
- type,
- categories,
- format_version: formatVersion,
- data_streams: dataStreams,
- policy_templates: policyTemplates,
- };
-}
-
-function parseAndVerifyDataStreams(
- paths: string[],
- pkgName: string,
- pkgVersion: string
-): RegistryDataStream[] {
- // A data stream is made up of a subdirectory of name-version/data_stream/, containing a manifest.yml
- let dataStreamPaths: string[] = [];
- const dataStreams: RegistryDataStream[] = [];
- const pkgKey = pkgToPkgKey({ name: pkgName, version: pkgVersion });
-
- // pick all paths matching name-version/data_stream/DATASTREAM_PATH/...
- // from those, pick all unique data stream paths
- paths
- .filter((path) => path.startsWith(`${pkgKey}/data_stream/`))
- .forEach((path) => {
- const parts = path.split('/');
- if (parts.length > 2 && parts[2]) dataStreamPaths.push(parts[2]);
- });
-
- dataStreamPaths = uniq(dataStreamPaths);
+export const deletePackageCache = (name: string, version: string) => {
+ // get cached archive filelist
+ const paths = getArchiveFilelist(name, version);
- dataStreamPaths.forEach((dataStreamPath) => {
- const manifestFile = `${pkgKey}/data_stream/${dataStreamPath}/manifest.yml`;
- const manifestBuffer = cacheGet(manifestFile);
- if (!paths.includes(manifestFile) || !manifestBuffer) {
- throw new PackageInvalidArchiveError(
- `No manifest.yml file found for data stream '${dataStreamPath}'`
- );
- }
-
- let manifest;
- try {
- manifest = yaml.load(manifestBuffer.toString());
- } catch (error) {
- throw new PackageInvalidArchiveError(
- `Could not parse package manifest for data stream '${dataStreamPath}': ${error}.`
- );
- }
-
- const {
- title: dataStreamTitle,
- release,
- ingest_pipeline: ingestPipeline,
- type,
- dataset,
- } = manifest;
- if (!(dataStreamTitle && release && type)) {
- throw new PackageInvalidArchiveError(
- `Invalid manifest for data stream '${dataStreamPath}': one or more fields missing of 'title', 'release', 'type'`
- );
- }
- const streams = parseAndVerifyStreams(manifest, dataStreamPath);
-
- // default ingest pipeline name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L26
- return dataStreams.push({
- dataset: dataset || `${pkgName}.${dataStreamPath}`,
- title: dataStreamTitle,
- release,
- package: pkgName,
- ingest_pipeline: ingestPipeline || 'default',
- path: dataStreamPath,
- type,
- streams,
- });
- });
+ // delete cached archive filelist
+ deleteArchiveFilelist(name, version);
- return dataStreams;
-}
-
-function parseAndVerifyStreams(manifest: any, dataStreamPath: string): RegistryStream[] {
- const streams: RegistryStream[] = [];
- const manifestStreams = manifest.streams;
- if (manifestStreams && manifestStreams.length > 0) {
- manifestStreams.forEach((manifestStream: any) => {
- const {
- input,
- title: streamTitle,
- description,
- enabled,
- vars: manifestVars,
- template_path: templatePath,
- } = manifestStream;
- if (!(input && streamTitle)) {
- throw new PackageInvalidArchiveError(
- `Invalid manifest for data stream ${dataStreamPath}: stream is missing one or more fields of: input, title`
- );
- }
- const vars = parseAndVerifyVars(manifestVars, `data stream ${dataStreamPath}`);
- // default template path name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L143
- streams.push({
- input,
- title: streamTitle,
- description,
- enabled,
- vars,
- template_path: templatePath || 'stream.yml.hbs',
- });
- });
- }
- return streams;
-}
-
-function parseAndVerifyVars(manifestVars: any[], location: string): RegistryVarsEntry[] {
- const vars: RegistryVarsEntry[] = [];
- if (manifestVars && manifestVars.length > 0) {
- manifestVars.forEach((manifestVar) => {
- const {
- name,
- title: varTitle,
- description,
- type,
- required,
- show_user: showUser,
- multi,
- def,
- os,
- } = manifestVar;
- if (!(name && type)) {
- throw new PackageInvalidArchiveError(
- `Invalid var definition for ${location}: one of mandatory fields 'name' and 'type' missing in var: ${manifestVar}`
- );
- }
- vars.push({
- name,
- title: varTitle,
- description,
- type,
- required,
- show_user: showUser,
- multi,
- default: def,
- os,
- });
- });
- }
- return vars;
-}
-
-function parseAndVerifyPolicyTemplates(manifest: any): RegistryPolicyTemplate[] {
- const policyTemplates: RegistryPolicyTemplate[] = [];
- const manifestPolicyTemplates = manifest.policy_templates;
- if (manifestPolicyTemplates && manifestPolicyTemplates > 0) {
- manifestPolicyTemplates.forEach((policyTemplate: any) => {
- const { name, title: policyTemplateTitle, description, inputs, multiple } = policyTemplate;
- if (!(name && policyTemplateTitle && description && inputs)) {
- throw new PackageInvalidArchiveError(
- `Invalid top-level manifest: one of mandatory fields 'name', 'title', 'description', 'input' missing in policy template: ${policyTemplate}`
- );
- }
-
- const parsedInputs = parseAndVerifyInputs(inputs, `config template ${name}`);
-
- // defaults to true if undefined, but may be explicitly set to false.
- let parsedMultiple = true;
- if (typeof multiple === 'boolean' && multiple === false) parsedMultiple = false;
-
- policyTemplates.push({
- name,
- title: policyTemplateTitle,
- description,
- inputs: parsedInputs,
- multiple: parsedMultiple,
- });
- });
- }
- return policyTemplates;
-}
-
-function parseAndVerifyInputs(manifestInputs: any, location: string): RegistryInput[] {
- const inputs: RegistryInput[] = [];
- if (manifestInputs && manifestInputs.length > 0) {
- manifestInputs.forEach((input: any) => {
- const { type, title: inputTitle, description, vars } = input;
- if (!(type && inputTitle)) {
- throw new PackageInvalidArchiveError(
- `Invalid top-level manifest: one of mandatory fields 'type', 'title' missing in input: ${input}`
- );
- }
- const parsedVars = parseAndVerifyVars(vars, location);
- inputs.push({
- type,
- title: inputTitle,
- description,
- vars: parsedVars,
- });
- });
- }
- return inputs;
-}
+ // delete cached archive files
+ // this has been populated in unpackArchiveToCache()
+ paths?.forEach((path) => cacheDelete(path));
+};
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts
new file mode 100644
index 0000000000000..90941aaf80cdd
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts
@@ -0,0 +1,262 @@
+/*
+ * 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 yaml from 'js-yaml';
+import { uniq } from 'lodash';
+import {
+ ArchivePackage,
+ RegistryPolicyTemplate,
+ RegistryDataStream,
+ RegistryInput,
+ RegistryStream,
+ RegistryVarsEntry,
+} from '../../../../common/types';
+import { PackageInvalidArchiveError } from '../../../errors';
+import { pkgToPkgKey } from '../registry';
+import { cacheGet } from './cache';
+
+// TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the
+// package registry. At some point this should probably be replaced (or enhanced) with verification based on
+// https://github.com/elastic/package-spec/
+export function parseAndVerifyArchive(paths: string[]): ArchivePackage {
+ // The top-level directory must match pkgName-pkgVersion, and no other top-level files or directories may be present
+ const toplevelDir = paths[0].split('/')[0];
+ paths.forEach((path) => {
+ if (path.split('/')[0] !== toplevelDir) {
+ throw new PackageInvalidArchiveError('Package contains more than one top-level directory.');
+ }
+ });
+
+ // The package must contain a manifest file ...
+ const manifestFile = `${toplevelDir}/manifest.yml`;
+ const manifestBuffer = cacheGet(manifestFile);
+ if (!paths.includes(manifestFile) || !manifestBuffer) {
+ throw new PackageInvalidArchiveError('Package must contain a top-level manifest.yml file.');
+ }
+
+ // ... which must be valid YAML
+ let manifest;
+ try {
+ manifest = yaml.load(manifestBuffer.toString());
+ } catch (error) {
+ throw new PackageInvalidArchiveError(`Could not parse top-level package manifest: ${error}.`);
+ }
+
+ // Package name and version from the manifest must match those from the toplevel directory
+ const pkgKey = pkgToPkgKey({ name: manifest.name, version: manifest.version });
+ if (toplevelDir !== pkgKey) {
+ throw new PackageInvalidArchiveError(
+ `Name ${manifest.name} and version ${manifest.version} do not match top-level directory ${toplevelDir}`
+ );
+ }
+
+ const { name, version, description, type, categories, format_version: formatVersion } = manifest;
+ // check for mandatory fields
+ if (!(name && version && description && type && categories && formatVersion)) {
+ throw new PackageInvalidArchiveError(
+ 'Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version'
+ );
+ }
+
+ const dataStreams = parseAndVerifyDataStreams(paths, name, version);
+ const policyTemplates = parseAndVerifyPolicyTemplates(manifest);
+
+ return {
+ name,
+ version,
+ description,
+ type,
+ categories,
+ format_version: formatVersion,
+ data_streams: dataStreams,
+ policy_templates: policyTemplates,
+ };
+}
+function parseAndVerifyDataStreams(
+ paths: string[],
+ pkgName: string,
+ pkgVersion: string
+): RegistryDataStream[] {
+ // A data stream is made up of a subdirectory of name-version/data_stream/, containing a manifest.yml
+ let dataStreamPaths: string[] = [];
+ const dataStreams: RegistryDataStream[] = [];
+ const pkgKey = pkgToPkgKey({ name: pkgName, version: pkgVersion });
+
+ // pick all paths matching name-version/data_stream/DATASTREAM_PATH/...
+ // from those, pick all unique data stream paths
+ paths
+ .filter((path) => path.startsWith(`${pkgKey}/data_stream/`))
+ .forEach((path) => {
+ const parts = path.split('/');
+ if (parts.length > 2 && parts[2]) dataStreamPaths.push(parts[2]);
+ });
+
+ dataStreamPaths = uniq(dataStreamPaths);
+
+ dataStreamPaths.forEach((dataStreamPath) => {
+ const manifestFile = `${pkgKey}/data_stream/${dataStreamPath}/manifest.yml`;
+ const manifestBuffer = cacheGet(manifestFile);
+ if (!paths.includes(manifestFile) || !manifestBuffer) {
+ throw new PackageInvalidArchiveError(
+ `No manifest.yml file found for data stream '${dataStreamPath}'`
+ );
+ }
+
+ let manifest;
+ try {
+ manifest = yaml.load(manifestBuffer.toString());
+ } catch (error) {
+ throw new PackageInvalidArchiveError(
+ `Could not parse package manifest for data stream '${dataStreamPath}': ${error}.`
+ );
+ }
+
+ const {
+ title: dataStreamTitle,
+ release,
+ ingest_pipeline: ingestPipeline,
+ type,
+ dataset,
+ } = manifest;
+ if (!(dataStreamTitle && release && type)) {
+ throw new PackageInvalidArchiveError(
+ `Invalid manifest for data stream '${dataStreamPath}': one or more fields missing of 'title', 'release', 'type'`
+ );
+ }
+ const streams = parseAndVerifyStreams(manifest, dataStreamPath);
+
+ // default ingest pipeline name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L26
+ return dataStreams.push({
+ dataset: dataset || `${pkgName}.${dataStreamPath}`,
+ title: dataStreamTitle,
+ release,
+ package: pkgName,
+ ingest_pipeline: ingestPipeline || 'default',
+ path: dataStreamPath,
+ type,
+ streams,
+ });
+ });
+
+ return dataStreams;
+}
+function parseAndVerifyStreams(manifest: any, dataStreamPath: string): RegistryStream[] {
+ const streams: RegistryStream[] = [];
+ const manifestStreams = manifest.streams;
+ if (manifestStreams && manifestStreams.length > 0) {
+ manifestStreams.forEach((manifestStream: any) => {
+ const {
+ input,
+ title: streamTitle,
+ description,
+ enabled,
+ vars: manifestVars,
+ template_path: templatePath,
+ } = manifestStream;
+ if (!(input && streamTitle)) {
+ throw new PackageInvalidArchiveError(
+ `Invalid manifest for data stream ${dataStreamPath}: stream is missing one or more fields of: input, title`
+ );
+ }
+ const vars = parseAndVerifyVars(manifestVars, `data stream ${dataStreamPath}`);
+ // default template path name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L143
+ streams.push({
+ input,
+ title: streamTitle,
+ description,
+ enabled,
+ vars,
+ template_path: templatePath || 'stream.yml.hbs',
+ });
+ });
+ }
+ return streams;
+}
+function parseAndVerifyVars(manifestVars: any[], location: string): RegistryVarsEntry[] {
+ const vars: RegistryVarsEntry[] = [];
+ if (manifestVars && manifestVars.length > 0) {
+ manifestVars.forEach((manifestVar) => {
+ const {
+ name,
+ title: varTitle,
+ description,
+ type,
+ required,
+ show_user: showUser,
+ multi,
+ def,
+ os,
+ } = manifestVar;
+ if (!(name && type)) {
+ throw new PackageInvalidArchiveError(
+ `Invalid var definition for ${location}: one of mandatory fields 'name' and 'type' missing in var: ${manifestVar}`
+ );
+ }
+ vars.push({
+ name,
+ title: varTitle,
+ description,
+ type,
+ required,
+ show_user: showUser,
+ multi,
+ default: def,
+ os,
+ });
+ });
+ }
+ return vars;
+}
+function parseAndVerifyPolicyTemplates(manifest: any): RegistryPolicyTemplate[] {
+ const policyTemplates: RegistryPolicyTemplate[] = [];
+ const manifestPolicyTemplates = manifest.policy_templates;
+ if (manifestPolicyTemplates && manifestPolicyTemplates > 0) {
+ manifestPolicyTemplates.forEach((policyTemplate: any) => {
+ const { name, title: policyTemplateTitle, description, inputs, multiple } = policyTemplate;
+ if (!(name && policyTemplateTitle && description && inputs)) {
+ throw new PackageInvalidArchiveError(
+ `Invalid top-level manifest: one of mandatory fields 'name', 'title', 'description', 'input' missing in policy template: ${policyTemplate}`
+ );
+ }
+
+ const parsedInputs = parseAndVerifyInputs(inputs, `config template ${name}`);
+
+ // defaults to true if undefined, but may be explicitly set to false.
+ let parsedMultiple = true;
+ if (typeof multiple === 'boolean' && multiple === false) parsedMultiple = false;
+
+ policyTemplates.push({
+ name,
+ title: policyTemplateTitle,
+ description,
+ inputs: parsedInputs,
+ multiple: parsedMultiple,
+ });
+ });
+ }
+ return policyTemplates;
+}
+function parseAndVerifyInputs(manifestInputs: any, location: string): RegistryInput[] {
+ const inputs: RegistryInput[] = [];
+ if (manifestInputs && manifestInputs.length > 0) {
+ manifestInputs.forEach((input: any) => {
+ const { type, title: inputTitle, description, vars } = input;
+ if (!(type && inputTitle)) {
+ throw new PackageInvalidArchiveError(
+ `Invalid top-level manifest: one of mandatory fields 'type', 'title' missing in input: ${input}`
+ );
+ }
+ const parsedVars = parseAndVerifyVars(vars, location);
+ inputs.push({
+ type,
+ title: inputTitle,
+ description,
+ vars: parsedVars,
+ });
+ });
+ }
+ return inputs;
+}
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts
index abd2ba777e516..dcd8846fa96a4 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts
@@ -547,4 +547,77 @@ describe('processFields', () => {
];
expect(processFields(nested)).toEqual(nestedExpected);
});
+
+ test('ignores redefinitions of a field', () => {
+ const fields = [
+ {
+ name: 'a',
+ type: 'text',
+ },
+ {
+ name: 'a',
+ type: 'number',
+ },
+ {
+ name: 'b.c',
+ type: 'number',
+ },
+ {
+ name: 'b',
+ type: 'group',
+ fields: [
+ {
+ name: 'c',
+ type: 'text',
+ },
+ ],
+ },
+ ];
+
+ const fieldsExpected = [
+ {
+ name: 'a',
+ // should preserve the field that was parsed first which had type: text
+ type: 'text',
+ },
+ {
+ name: 'b',
+ type: 'group',
+ fields: [
+ {
+ name: 'c',
+ // should preserve the field that was parsed first which had type: number
+ type: 'number',
+ },
+ ],
+ },
+ ];
+ expect(processFields(fields)).toEqual(fieldsExpected);
+ });
+
+ test('ignores multiple redefinitions of a field', () => {
+ const fields = [
+ {
+ name: 'a',
+ type: 'text',
+ },
+ {
+ name: 'a',
+ type: 'number',
+ },
+ {
+ name: 'a',
+ type: 'keyword',
+ },
+ ];
+
+ const fieldsExpected = [
+ {
+ name: 'a',
+ // should preserve the field that was parsed first which had type: text
+ type: 'text',
+ },
+ ];
+ expect(processFields(fields)).toEqual(fieldsExpected);
+ });
});
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts
index 5d3e8e9ce87d1..b7650d10b6b25 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts
@@ -61,11 +61,7 @@ describe('_installPackage', () => {
const installationPromise = _installPackage({
savedObjectsClient: soClient,
callCluster,
- pkgName: 'abc',
- pkgVersion: '1.2.3',
paths: [],
- removable: false,
- internal: false,
packageInfo: {
name: 'xyz',
version: '4.5.6',
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts
index f570984cc61aa..a83d9428b7c93 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts
@@ -21,6 +21,7 @@ import { installPipelines, deletePreviousPipelines } from '../elasticsearch/inge
import { installILMPolicy } from '../elasticsearch/ilm/install';
import { installKibanaAssets, getKibanaAssets } from '../kibana/assets/install';
import { updateCurrentWriteIndices } from '../elasticsearch/template/template';
+import { isRequiredPackage } from './index';
import { deleteKibanaSavedObjectsAssets } from './remove';
import { installTransform } from '../elasticsearch/transform/install';
import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './install';
@@ -32,28 +33,22 @@ import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './insta
export async function _installPackage({
savedObjectsClient,
callCluster,
- pkgName,
- pkgVersion,
installedPkg,
paths,
- removable,
- internal,
packageInfo,
installType,
installSource,
}: {
savedObjectsClient: SavedObjectsClientContract;
callCluster: CallESAsCurrentUser;
- pkgName: string;
- pkgVersion: string;
installedPkg?: SavedObject;
paths: string[];
- removable: boolean;
- internal: boolean;
packageInfo: InstallablePackage;
installType: InstallType;
installSource: InstallSource;
}): Promise {
+ const { internal = false, name: pkgName, version: pkgVersion } = packageInfo;
+ const removable = !isRequiredPackage(pkgName);
const toSaveESIndexPatterns = generateESIndexPatterns(packageInfo.data_streams);
// add the package installation to the saved object.
// if some installation already exists, just update install info
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts
index eb43bef72db70..ab93a73a55f39 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts
@@ -6,9 +6,9 @@
import { InstallablePackage } from '../../../types';
import { getAssets } from './assets';
-import { getArchiveFilelist } from '../registry/cache';
+import { getArchiveFilelist } from '../archive/cache';
-jest.mock('../registry/cache', () => {
+jest.mock('../archive/cache', () => {
return {
getArchiveFilelist: jest.fn(),
};
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts
index 856f04c0c9b67..2e2090312c9ae 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts
@@ -6,7 +6,7 @@
import { InstallablePackage } from '../../../types';
import * as Registry from '../registry';
-import { getArchiveFilelist } from '../registry/cache';
+import { getArchiveFilelist } from '../archive/cache';
// paths from RegistryPackage are routes to the assets on EPR
// e.g. `/package/nginx/1.2.0/data_stream/access/fields/fields.yml`
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts
index 2021b353f1a27..893df1733c58b 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts
@@ -116,7 +116,7 @@ export async function getPackageInfo(options: {
] = await Promise.all([
getInstallationObject({ savedObjectsClient, pkgName }),
Registry.fetchFindLatestPackage(pkgName),
- Registry.loadRegistryPackage(pkgName, pkgVersion),
+ Registry.getRegistryPackage(pkgName, pkgVersion),
]);
// add properties that aren't (or aren't yet) on Registry response
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts
index 410a9c0b22537..a1128011d81e6 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts
@@ -29,8 +29,7 @@ export {
BulkInstallResponse,
IBulkInstallPackageError,
handleInstallPackageFailure,
- installPackageFromRegistry,
- installPackageByUpload,
+ installPackage,
ensureInstalledPackage,
} from './install';
export { removeInstallation } from './remove';
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts
index 0496a6e9aeef1..00a5c689e906d 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts
@@ -24,7 +24,6 @@ import * as Registry from '../registry';
import {
getInstallation,
getInstallationObject,
- isRequiredPackage,
bulkInstallPackages,
isBulkInstallError,
} from './index';
@@ -52,7 +51,7 @@ export async function installLatestPackage(options: {
name: latestPackage.name,
version: latestPackage.version,
});
- return installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster });
+ return installPackage({ installSource: 'registry', savedObjectsClient, pkgkey, callCluster });
} catch (err) {
throw err;
}
@@ -148,7 +147,8 @@ export async function handleInstallPackageFailure({
}
const prevVersion = `${pkgName}-${installedPkg.attributes.version}`;
logger.error(`rolling back to ${prevVersion} after error installing ${pkgkey}`);
- await installPackageFromRegistry({
+ await installPackage({
+ installSource: 'registry',
savedObjectsClient,
pkgkey: prevVersion,
callCluster,
@@ -186,7 +186,12 @@ export async function upgradePackage({
});
try {
- const assets = await installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster });
+ const assets = await installPackage({
+ installSource: 'registry',
+ savedObjectsClient,
+ pkgkey,
+ callCluster,
+ });
return {
name: pkgToUpgrade,
newVersion: latestPkg.version,
@@ -218,19 +223,19 @@ export async function upgradePackage({
}
}
-interface InstallPackageParams {
+interface InstallRegistryPackageParams {
savedObjectsClient: SavedObjectsClientContract;
pkgkey: string;
callCluster: CallESAsCurrentUser;
force?: boolean;
}
-export async function installPackageFromRegistry({
+async function installPackageFromRegistry({
savedObjectsClient,
pkgkey,
callCluster,
force = false,
-}: InstallPackageParams): Promise {
+}: InstallRegistryPackageParams): Promise {
// TODO: change epm API to /packageName/version so we don't need to do this
const { pkgName, pkgVersion } = Registry.splitPkgKey(pkgkey);
// TODO: calls to getInstallationObject, Registry.fetchInfo, and Registry.fetchFindLatestPackge
@@ -248,39 +253,38 @@ export async function installPackageFromRegistry({
throw new PackageOutdatedError(`${pkgkey} is out-of-date and cannot be installed or updated`);
}
- const { paths, registryPackageInfo } = await Registry.loadRegistryPackage(pkgName, pkgVersion);
-
- const removable = !isRequiredPackage(pkgName);
- const { internal = false } = registryPackageInfo;
- const installSource = 'registry';
+ const { paths, registryPackageInfo } = await Registry.getRegistryPackage(pkgName, pkgVersion);
return _installPackage({
savedObjectsClient,
callCluster,
- pkgName,
- pkgVersion,
installedPkg,
paths,
- removable,
- internal,
packageInfo: registryPackageInfo,
installType,
- installSource,
+ installSource: 'registry',
});
}
-export async function installPackageByUpload({
- savedObjectsClient,
- callCluster,
- archiveBuffer,
- contentType,
-}: {
+interface InstallUploadedArchiveParams {
savedObjectsClient: SavedObjectsClientContract;
callCluster: CallESAsCurrentUser;
archiveBuffer: Buffer;
contentType: string;
-}): Promise {
+}
+
+export type InstallPackageParams =
+ | ({ installSource: Extract } & InstallRegistryPackageParams)
+ | ({ installSource: Extract } & InstallUploadedArchiveParams);
+
+async function installPackageByUpload({
+ savedObjectsClient,
+ callCluster,
+ archiveBuffer,
+ contentType,
+}: InstallUploadedArchiveParams): Promise {
const { paths, archivePackageInfo } = await loadArchivePackage({ archiveBuffer, contentType });
+
const installedPkg = await getInstallationObject({
savedObjectsClient,
pkgName: archivePackageInfo.name,
@@ -292,25 +296,45 @@ export async function installPackageByUpload({
);
}
- const removable = !isRequiredPackage(archivePackageInfo.name);
- const { internal = false } = archivePackageInfo;
- const installSource = 'upload';
-
return _installPackage({
savedObjectsClient,
callCluster,
- pkgName: archivePackageInfo.name,
- pkgVersion: archivePackageInfo.version,
installedPkg,
paths,
- removable,
- internal,
packageInfo: archivePackageInfo,
installType,
- installSource,
+ installSource: 'upload',
});
}
+export async function installPackage(args: InstallPackageParams) {
+ if (!('installSource' in args)) {
+ throw new Error('installSource is required');
+ }
+
+ if (args.installSource === 'registry') {
+ const { savedObjectsClient, pkgkey, callCluster, force } = args;
+
+ return installPackageFromRegistry({
+ savedObjectsClient,
+ pkgkey,
+ callCluster,
+ force,
+ });
+ } else if (args.installSource === 'upload') {
+ const { savedObjectsClient, callCluster, archiveBuffer, contentType } = args;
+
+ return installPackageByUpload({
+ savedObjectsClient,
+ callCluster,
+ archiveBuffer,
+ contentType,
+ });
+ }
+ // @ts-expect-error s/b impossibe b/c `never` by this point, but just in case
+ throw new Error(`Unknown installSource: ${args.installSource}`);
+}
+
export const updateVersion = async (
savedObjectsClient: SavedObjectsClientContract,
pkgName: string,
@@ -421,7 +445,9 @@ export async function ensurePackagesCompletedInstall(
const pkgkey = `${pkg.attributes.name}-${pkg.attributes.install_version}`;
// reinstall package
if (elapsedTime > MAX_TIME_COMPLETE_INSTALL) {
- acc.push(installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster }));
+ acc.push(
+ installPackage({ installSource: 'registry', savedObjectsClient, pkgkey, callCluster })
+ );
}
return acc;
}, []);
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts
index 5db47adc983c2..9fabbaf72474e 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts
@@ -21,7 +21,8 @@ import { deletePipeline } from '../elasticsearch/ingest_pipeline/';
import { installIndexPatterns } from '../kibana/index_pattern/install';
import { deleteTransforms } from '../elasticsearch/transform/remove';
import { packagePolicyService, appContextService } from '../..';
-import { splitPkgKey, deletePackageCache } from '../registry';
+import { splitPkgKey } from '../registry';
+import { deletePackageCache } from '../archive';
export async function removeInstallation(options: {
savedObjectsClient: SavedObjectsClientContract;
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts
index 6d029b54a6317..b79218638ce24 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts
@@ -17,7 +17,7 @@ export async function untarBuffer(
buffer: Buffer,
filter = (entry: ArchiveEntry): boolean => true,
onEntry = (entry: ArchiveEntry): void => {}
-): Promise {
+): Promise {
const deflatedStream = bufferToStream(buffer);
// use tar.list vs .extract to avoid writing to disk
const inflateStream = tar.list().on('entry', (entry: tar.FileStat) => {
@@ -36,7 +36,7 @@ export async function unzipBuffer(
buffer: Buffer,
filter = (entry: ArchiveEntry): boolean => true,
onEntry = (entry: ArchiveEntry): void => {}
-): Promise {
+): Promise {
const zipfile = await yauzlFromBuffer(buffer, { lazyEntries: true });
zipfile.readEntry();
zipfile.on('entry', async (entry: yauzl.Entry) => {
@@ -50,6 +50,26 @@ export async function unzipBuffer(
return new Promise((resolve, reject) => zipfile.on('end', resolve).on('error', reject));
}
+type BufferExtractor = typeof unzipBuffer | typeof untarBuffer;
+export function getBufferExtractor(
+ args: { contentType: string } | { archivePath: string }
+): BufferExtractor | undefined {
+ if ('contentType' in args) {
+ if (args.contentType === 'application/gzip') {
+ return untarBuffer;
+ } else if (args.contentType === 'application/zip') {
+ return unzipBuffer;
+ }
+ } else if ('archivePath' in args) {
+ if (args.archivePath.endsWith('.zip')) {
+ return unzipBuffer;
+ }
+ if (args.archivePath.endsWith('.gz')) {
+ return untarBuffer;
+ }
+ }
+}
+
function yauzlFromBuffer(buffer: Buffer, opts: yauzl.Options): Promise {
return new Promise((resolve, reject) =>
yauzl.fromBuffer(buffer, opts, (err?: Error, handle?: yauzl.ZipFile) =>
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts
index ba51636c13f36..a2d5c8147002d 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts
@@ -82,14 +82,38 @@ describe('splitPkgKey tests', () => {
});
});
-describe('getBufferExtractor', () => {
- it('returns unzipBuffer if the archive key ends in .zip', () => {
- const extractor = getBufferExtractor('.zip');
+describe('getBufferExtractor called with { archivePath }', () => {
+ it('returns unzipBuffer if `archivePath` ends in .zip', () => {
+ const extractor = getBufferExtractor({ archivePath: '.zip' });
expect(extractor).toBe(unzipBuffer);
});
- it('returns untarBuffer if the key ends in anything else', () => {
- const extractor = getBufferExtractor('.xyz');
+ it('returns untarBuffer if `archivePath` ends in .gz', () => {
+ const extractor = getBufferExtractor({ archivePath: '.gz' });
expect(extractor).toBe(untarBuffer);
+ const extractor2 = getBufferExtractor({ archivePath: '.tar.gz' });
+ expect(extractor2).toBe(untarBuffer);
+ });
+
+ it('returns `undefined` if `archivePath` ends in anything else', () => {
+ const extractor = getBufferExtractor({ archivePath: '.xyz' });
+ expect(extractor).toEqual(undefined);
+ });
+});
+
+describe('getBufferExtractor called with { contentType }', () => {
+ it('returns unzipBuffer if `contentType` is `application/zip`', () => {
+ const extractor = getBufferExtractor({ contentType: 'application/zip' });
+ expect(extractor).toBe(unzipBuffer);
+ });
+
+ it('returns untarBuffer if `contentType` is `application/gzip`', () => {
+ const extractor = getBufferExtractor({ contentType: 'application/gzip' });
+ expect(extractor).toBe(untarBuffer);
+ });
+
+ it('returns `undefined` if `contentType` ends in anything else', () => {
+ const extractor = getBufferExtractor({ contentType: '.xyz' });
+ expect(extractor).toEqual(undefined);
});
});
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts
index 0172f3bb38f51..52a1894570b2a 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
+import mime from 'mime-types';
import semver from 'semver';
import { Response } from 'node-fetch';
import { URL } from 'url';
@@ -18,22 +18,15 @@ import {
RegistrySearchResults,
RegistrySearchResult,
} from '../../../types';
-import {
- cacheGet,
- cacheSet,
- cacheDelete,
- getArchiveFilelist,
- setArchiveFilelist,
- deleteArchiveFilelist,
-} from './cache';
-import { ArchiveEntry, untarBuffer, unzipBuffer } from './extract';
+import { unpackArchiveToCache } from '../archive';
+import { cacheGet, getArchiveFilelist, setArchiveFilelist } from '../archive';
import { fetchUrl, getResponse, getResponseStream } from './requests';
import { streamToBuffer } from './streams';
import { getRegistryUrl } from './registry_url';
import { appContextService } from '../..';
import { PackageNotFoundError, PackageCacheError } from '../../../errors';
-export { ArchiveEntry } from './extract';
+export { ArchiveEntry, getBufferExtractor } from './extract';
export interface SearchParams {
category?: CategoryId;
@@ -132,34 +125,18 @@ export async function fetchCategories(params?: CategoriesParams): Promise true
-): Promise {
- const paths: string[] = [];
- const { archiveBuffer, archivePath } = await fetchArchiveBuffer(pkgName, pkgVersion);
- const bufferExtractor = getBufferExtractor(archivePath);
- await bufferExtractor(archiveBuffer, filter, (entry: ArchiveEntry) => {
- const { path, buffer } = entry;
- const { file } = pathParts(path);
- if (!file) return;
- if (buffer) {
- cacheSet(path, buffer);
- paths.push(path);
- }
- });
-
- return paths;
-}
-
-export async function loadRegistryPackage(
+export async function getRegistryPackage(
pkgName: string,
pkgVersion: string
): Promise<{ paths: string[]; registryPackageInfo: RegistryPackage }> {
let paths = getArchiveFilelist(pkgName, pkgVersion);
if (!paths || paths.length === 0) {
- paths = await unpackRegistryPackageToCache(pkgName, pkgVersion);
+ const { archiveBuffer, archivePath } = await fetchArchiveBuffer(pkgName, pkgVersion);
+ const contentType = mime.lookup(archivePath);
+ if (!contentType) {
+ throw new Error(`Unknown compression format for '${archivePath}'. Please use .zip or .gz`);
+ }
+ paths = await unpackArchiveToCache(archiveBuffer, contentType);
setArchiveFilelist(pkgName, pkgVersion, paths);
}
@@ -199,13 +176,6 @@ export function pathParts(path: string): AssetParts {
} as AssetParts;
}
-export function getBufferExtractor(archivePath: string) {
- const isZip = archivePath.endsWith('.zip');
- const bufferExtractor = isZip ? unzipBuffer : untarBuffer;
-
- return bufferExtractor;
-}
-
export async function ensureCachedArchiveInfo(
name: string,
version: string,
@@ -214,7 +184,7 @@ export async function ensureCachedArchiveInfo(
const paths = getArchiveFilelist(name, version);
if (!paths || paths.length === 0) {
if (installSource === 'registry') {
- await loadRegistryPackage(name, version);
+ await getRegistryPackage(name, version);
} else {
throw new PackageCacheError(
`Package ${name}-${version} not cached. If it was uploaded, try uninstalling and reinstalling manually.`
@@ -261,15 +231,3 @@ export function groupPathsByService(paths: string[]): AssetsGroupedByServiceByTy
// elasticsearch: assets.elasticsearch,
};
}
-
-export const deletePackageCache = (name: string, version: string) => {
- // get cached archive filelist
- const paths = getArchiveFilelist(name, version);
-
- // delete cached archive filelist
- deleteArchiveFilelist(name, version);
-
- // delete cached archive files
- // this has been populated in unpackRegistryPackageToCache()
- paths?.forEach((path) => cacheDelete(path));
-};
diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts b/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts
index 6064e5bae0634..6ae76c56436d5 100644
--- a/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts
+++ b/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts
@@ -34,6 +34,12 @@ jest.mock('./epm/packages/assets', () => {
};
});
+jest.mock('./epm/packages', () => {
+ return {
+ getPackageInfo: () => ({}),
+ };
+});
+
jest.mock('./epm/registry', () => {
return {
fetchInfo: () => ({}),
diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.ts b/x-pack/plugins/ingest_manager/server/services/package_policy.ts
index dc3a4495191c9..0f78c97a6f2bd 100644
--- a/x-pack/plugins/ingest_manager/server/services/package_policy.ts
+++ b/x-pack/plugins/ingest_manager/server/services/package_policy.ts
@@ -4,10 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { SavedObjectsClientContract } from 'src/core/server';
+import uuid from 'uuid';
import { AuthenticatedUser } from '../../../security/server';
import {
DeletePackagePoliciesResponse,
PackagePolicyInput,
+ NewPackagePolicyInput,
PackagePolicyInputStream,
PackageInfo,
ListWithKuery,
@@ -58,6 +60,11 @@ class PackagePolicyService {
throw new Error('There is already a package with the same name on this agent policy');
}
}
+ // Add ids to stream
+ const packagePolicyId = options?.id || uuid.v4();
+ let inputs: PackagePolicyInput[] = packagePolicy.inputs.map((input) =>
+ assignStreamIdToInput(packagePolicyId, input)
+ );
// Make sure the associated package is installed
if (packagePolicy.package?.name) {
@@ -85,7 +92,7 @@ class PackagePolicyService {
}
}
- packagePolicy.inputs = await this.assignPackageStream(pkgInfo, packagePolicy.inputs);
+ inputs = await this.assignPackageStream(pkgInfo, inputs);
}
const isoDate = new Date().toISOString();
@@ -93,13 +100,15 @@ class PackagePolicyService {
SAVED_OBJECT_TYPE,
{
...packagePolicy,
+ inputs,
revision: 1,
created_at: isoDate,
created_by: options?.user?.username ?? 'system',
updated_at: isoDate,
updated_by: options?.user?.username ?? 'system',
},
- options
+
+ { ...options, id: packagePolicyId }
);
// Assign it to the given agent policy
@@ -124,18 +133,28 @@ class PackagePolicyService {
const isoDate = new Date().toISOString();
// eslint-disable-next-line @typescript-eslint/naming-convention
const { saved_objects } = await soClient.bulkCreate(
- packagePolicies.map((packagePolicy) => ({
- type: SAVED_OBJECT_TYPE,
- attributes: {
- ...packagePolicy,
- policy_id: agentPolicyId,
- revision: 1,
- created_at: isoDate,
- created_by: options?.user?.username ?? 'system',
- updated_at: isoDate,
- updated_by: options?.user?.username ?? 'system',
- },
- }))
+ packagePolicies.map((packagePolicy) => {
+ const packagePolicyId = uuid.v4();
+
+ const inputs = packagePolicy.inputs.map((input) =>
+ assignStreamIdToInput(packagePolicyId, input)
+ );
+
+ return {
+ type: SAVED_OBJECT_TYPE,
+ id: packagePolicyId,
+ attributes: {
+ ...packagePolicy,
+ inputs,
+ policy_id: agentPolicyId,
+ revision: 1,
+ created_at: isoDate,
+ created_by: options?.user?.username ?? 'system',
+ updated_at: isoDate,
+ updated_by: options?.user?.username ?? 'system',
+ },
+ };
+ })
);
// Filter out invalid SOs
@@ -255,11 +274,26 @@ class PackagePolicyService {
}
}
+ let inputs = await restOfPackagePolicy.inputs.map((input) =>
+ assignStreamIdToInput(oldPackagePolicy.id, input)
+ );
+
+ if (packagePolicy.package?.name) {
+ const pkgInfo = await getPackageInfo({
+ savedObjectsClient: soClient,
+ pkgName: packagePolicy.package.name,
+ pkgVersion: packagePolicy.package.version,
+ });
+
+ inputs = await this.assignPackageStream(pkgInfo, inputs);
+ }
+
await soClient.update(
SAVED_OBJECT_TYPE,
id,
{
...restOfPackagePolicy,
+ inputs,
revision: oldPackagePolicy.revision + 1,
updated_at: new Date().toISOString(),
updated_by: options?.user?.username ?? 'system',
@@ -353,6 +387,15 @@ class PackagePolicyService {
}
}
+function assignStreamIdToInput(packagePolicyId: string, input: NewPackagePolicyInput) {
+ return {
+ ...input,
+ streams: input.streams.map((stream) => {
+ return { ...stream, id: `${input.type}-${stream.data_stream.dataset}-${packagePolicyId}` };
+ }),
+ };
+}
+
async function _assignPackageStreamToInput(
registryPkgInfo: RegistryPackage,
pkgInfo: PackageInfo,
diff --git a/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts b/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts
index 6673c12d51511..20d29c0aa18c9 100644
--- a/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts
+++ b/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts
@@ -54,7 +54,7 @@ const PackagePolicyBaseSchema = {
),
streams: schema.arrayOf(
schema.object({
- id: schema.string(),
+ id: schema.maybe(schema.string()), // BWC < 7.11
enabled: schema.boolean(),
data_stream: schema.object({ dataset: schema.string(), type: schema.string() }),
vars: schema.maybe(ConfigRecordSchema),
diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json
index 2da67f81122ab..ce78757676bcc 100644
--- a/x-pack/plugins/lens/kibana.json
+++ b/x-pack/plugins/lens/kibana.json
@@ -19,5 +19,5 @@
"optionalPlugins": ["usageCollection", "taskManager", "globalSearch", "savedObjectsTagging"],
"configPath": ["xpack", "lens"],
"extraPublicDirs": ["common/constants"],
- "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable"]
+ "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable", "lensOss"]
}
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx
index 82f753e3520b0..0332f11aa78b3 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx
@@ -93,6 +93,7 @@ export function LayerPanel(
state: props.visualizationState,
frame: props.framePublicAPI,
dateRange: props.framePublicAPI.dateRange,
+ activeData: props.framePublicAPI.activeData,
};
const datasourceId = datasourcePublicAPI.datasourceId;
const layerDatasourceState = props.datasourceStates[datasourceId].state;
@@ -111,6 +112,7 @@ export function LayerPanel(
...layerDatasourceDropProps,
frame: props.framePublicAPI,
dateRange: props.framePublicAPI.dateRange,
+ activeData: props.framePublicAPI.activeData,
};
const { groups } = activeVisualization.getConfiguration(layerVisualizationConfigProps);
@@ -140,6 +142,7 @@ export function LayerPanel(
nativeProps={{
layerId,
state: layerDatasourceState,
+ activeData: props.framePublicAPI.activeData,
setState: (updater: unknown) => {
const newState =
typeof updater === 'function' ? updater(layerDatasourceState) : updater;
@@ -448,6 +451,7 @@ export function LayerPanel(
columnId: activeId,
filterOperations: activeGroup.filterOperations,
suggestedPriority: activeGroup?.suggestedPriority,
+ dimensionGroups: groups,
setState: (newState: unknown) => {
props.updateAll(
datasourceId,
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx
index f7a6f0597bf9c..b3ea14efbae80 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx
@@ -601,7 +601,8 @@ describe('editor_frame', () => {
setDatasourceState(updatedState);
});
- expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(2);
+ // validation requires to calls this getConfiguration API
+ expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(6);
expect(mockVisualization.getConfiguration).toHaveBeenLastCalledWith(
expect.objectContaining({
state: updatedState,
@@ -680,7 +681,8 @@ describe('editor_frame', () => {
setDatasourceState({});
});
- expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(2);
+ // validation requires to calls this getConfiguration API
+ expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(6);
expect(mockVisualization.getConfiguration).toHaveBeenLastCalledWith(
expect.objectContaining({
frame: expect.objectContaining({
@@ -1193,7 +1195,8 @@ describe('editor_frame', () => {
instance.find('[data-test-subj="lnsSuggestion"]').at(2).simulate('click');
});
- expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(1);
+ // validation requires to calls this getConfiguration API
+ expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(4);
expect(mockVisualization.getConfiguration).toHaveBeenCalledWith(
expect.objectContaining({
state: suggestionVisState,
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx
index bb40b9f31d254..935d65bfb6c08 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx
@@ -101,6 +101,7 @@ export function EditorFrame(props: EditorFrameProps) {
const framePublicAPI: FramePublicAPI = {
datasourceLayers,
+ activeData: state.activeData,
dateRange: props.dateRange,
query: props.query,
filters: props.filters,
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
index 28ad6c531e255..647c0f3ac9cca 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
@@ -6,7 +6,13 @@
import { SavedObjectReference } from 'kibana/public';
import { Ast } from '@kbn/interpreter/common';
-import { Datasource, DatasourcePublicAPI, FramePublicAPI, Visualization } from '../../types';
+import {
+ Datasource,
+ DatasourcePublicAPI,
+ FramePublicAPI,
+ Visualization,
+ VisualizationDimensionGroupConfig,
+} from '../../types';
import { buildExpression } from './expression_helpers';
import { Document } from '../../persistence/saved_object_store';
import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public';
@@ -104,8 +110,24 @@ export const validateDatasourceAndVisualization = (
longMessage: string;
}>
| undefined => {
+ const layersGroups =
+ currentVisualizationState &&
+ currentVisualization
+ ?.getLayerIds(currentVisualizationState)
+ .reduce>((memo, layerId) => {
+ const groups = currentVisualization?.getConfiguration({
+ frame: frameAPI,
+ layerId,
+ state: currentVisualizationState,
+ }).groups;
+ if (groups) {
+ memo[layerId] = groups;
+ }
+ return memo;
+ }, {});
+
const datasourceValidationErrors = currentDatasourceState
- ? currentDataSource?.getErrorMessages(currentDatasourceState)
+ ? currentDataSource?.getErrorMessages(currentDatasourceState, layersGroups)
: undefined;
const visualizationValidationErrors = currentVisualizationState
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts
index fc8daaed059dd..e0101493b27aa 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts
@@ -6,6 +6,7 @@
import { EditorFrameProps } from './index';
import { Document } from '../../persistence/saved_object_store';
+import { TableInspectorAdapter } from '../types';
export interface PreviewState {
visualization: {
@@ -21,6 +22,7 @@ export interface EditorFrameState extends PreviewState {
description?: string;
stagedPreview?: PreviewState;
activeDatasourceId: string | null;
+ activeData?: TableInspectorAdapter;
}
export type Action =
@@ -32,6 +34,10 @@ export type Action =
type: 'UPDATE_TITLE';
title: string;
}
+ | {
+ type: 'UPDATE_ACTIVE_DATA';
+ tables: TableInspectorAdapter;
+ }
| {
type: 'UPDATE_STATE';
// Just for diagnostics, so we can determine what action
@@ -139,6 +145,11 @@ export const reducer = (state: EditorFrameState, action: Action): EditorFrameSta
return { ...state, title: action.title };
case 'UPDATE_STATE':
return action.updater(state);
+ case 'UPDATE_ACTIVE_DATA':
+ return {
+ ...state,
+ activeData: action.tables,
+ };
case 'UPDATE_LAYER':
return {
...state,
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts
index 95057f9db7e93..daaf893f2a703 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts
@@ -7,6 +7,7 @@
import _ from 'lodash';
import { Ast } from '@kbn/interpreter/common';
import { IconType } from '@elastic/eui/src/components/icon/icon';
+import { Datatable } from 'src/plugins/expressions';
import { PaletteOutput } from 'src/plugins/charts/public';
import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public';
import {
@@ -50,6 +51,7 @@ export function getSuggestions({
visualizationState,
field,
visualizeTriggerFieldContext,
+ activeData,
mainPalette,
}: {
datasourceMap: Record;
@@ -66,6 +68,7 @@ export function getSuggestions({
visualizationState: unknown;
field?: unknown;
visualizeTriggerFieldContext?: VisualizeFieldContext;
+ activeData?: Record;
mainPalette?: PaletteOutput;
}): Suggestion[] {
const datasources = Object.entries(datasourceMap).filter(
@@ -87,7 +90,8 @@ export function getSuggestions({
dataSourceSuggestions = datasource.getDatasourceSuggestionsForField(datasourceState, field);
} else {
dataSourceSuggestions = datasource.getDatasourceSuggestionsFromCurrentState(
- datasourceState
+ datasourceState,
+ activeData
);
}
return dataSourceSuggestions.map((suggestion) => ({ ...suggestion, datasourceId }));
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
index 201c91ee91676..2e24b64ecca26 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
@@ -188,6 +188,7 @@ export function SuggestionPanel({
visualizationMap,
activeVisualizationId: currentVisualizationId,
visualizationState: currentVisualizationState,
+ activeData: frame.activeData,
})
.filter((suggestion) => !suggestion.hide)
.filter(
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
index fe8747de667a3..659626149aef2 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
@@ -325,6 +325,7 @@ function getTopSuggestion(
activeVisualizationId: props.visualizationId,
visualizationState: props.visualizationState,
subVisualizationId,
+ activeData: props.framePublicAPI.activeData,
mainPalette,
});
const suggestions = unfilteredSuggestions.filter((suggestion) => {
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx
index 447e94d09cdb2..231c38ea54048 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx
@@ -253,6 +253,48 @@ describe('workspace_panel', () => {
expect(trigger.exec).toHaveBeenCalledWith({ data: eventData });
});
+ it('should push add current data table to state on data$ emitting value', () => {
+ const framePublicAPI = createMockFramePublicAPI();
+ framePublicAPI.datasourceLayers = {
+ first: mockDatasource.publicAPIMock,
+ };
+ mockDatasource.toExpression.mockReturnValue('datasource');
+ mockDatasource.getLayers.mockReturnValue(['first']);
+ const dispatch = jest.fn();
+
+ instance = mount(
+ 'vis' },
+ }}
+ visualizationState={{}}
+ dispatch={dispatch}
+ ExpressionRenderer={expressionRendererMock}
+ core={coreMock.createSetup()}
+ plugins={{ uiActions: uiActionsMock, data: dataMock }}
+ />
+ );
+
+ const onData = expressionRendererMock.mock.calls[0][0].onData$!;
+
+ const tableData = { table1: { columns: [], rows: [] } };
+ onData(undefined, { tables: tableData });
+
+ expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_ACTIVE_DATA', tables: tableData });
+ });
+
it('should include data fetching for each layer in the expression', () => {
const mockDatasource2 = createMockDatasource('a');
const framePublicAPI = createMockFramePublicAPI();
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx
index e79060fb77329..e0dd3b3fe01ae 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx
@@ -50,6 +50,7 @@ import {
} from '../../../../../../../src/plugins/data/public';
import { WorkspacePanelWrapper } from './workspace_panel_wrapper';
import { DropIllustration } from '../../../assets/drop_illustration';
+import { LensInspectorAdapters } from '../../types';
import { getOriginalRequestErrorMessage } from '../../error_helper';
import { validateDatasourceAndVisualization } from '../state_helpers';
@@ -296,6 +297,7 @@ export function WorkspacePanel({
expression={expression}
framePublicAPI={framePublicAPI}
timefilter={plugins.data.query.timefilter.timefilter}
+ dispatch={dispatch}
onEvent={onEvent}
setLocalState={setLocalState}
localState={{ ...localState, configurationValidationError }}
@@ -309,7 +311,6 @@ export function WorkspacePanel({
title={title}
framePublicAPI={framePublicAPI}
dispatch={dispatch}
- emptyExpression={expression === null}
visualizationState={visualizationState}
visualizationId={activeVisualizationId}
datasourceStates={datasourceStates}
@@ -340,11 +341,13 @@ export const InnerVisualizationWrapper = ({
setLocalState,
localState,
ExpressionRendererComponent,
+ dispatch,
}: {
expression: Ast | null | undefined;
framePublicAPI: FramePublicAPI;
timefilter: TimefilterContract;
onEvent: (event: ExpressionRendererEvent) => void;
+ dispatch: (action: Action) => void;
setLocalState: (dispatch: (prevState: WorkspaceState) => WorkspaceState) => void;
localState: WorkspaceState & {
configurationValidationError?: Array<{ shortMessage: string; longMessage: string }>;
@@ -370,6 +373,18 @@ export const InnerVisualizationWrapper = ({
]
);
+ const onData$ = useCallback(
+ (data: unknown, inspectorAdapters?: LensInspectorAdapters) => {
+ if (inspectorAdapters && inspectorAdapters.tables) {
+ dispatch({
+ type: 'UPDATE_ACTIVE_DATA',
+ tables: inspectorAdapters.tables,
+ });
+ }
+ },
+ [dispatch]
+ );
+
if (localState.configurationValidationError) {
let showExtraErrors = null;
if (localState.configurationValidationError.length > 1) {
@@ -456,6 +471,7 @@ export const InnerVisualizationWrapper = ({
searchContext={context}
reload$={autoRefreshFetch$}
onEvent={onEvent}
+ onData$={onData$}
renderError={(errorMessage?: string | null, error?: ExpressionRenderError | null) => {
const visibleErrorMessage = getOriginalRequestErrorMessage(error) || errorMessage;
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss
index 33b9b2fe1dbf0..ae9294c474b42 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.scss
@@ -11,17 +11,6 @@
position: relative; // For positioning the dnd overlay
min-height: $euiSizeXXL * 10;
- .lnsWorkspacePanelWrapper__pageContentHeader {
- @include euiTitle('xs');
- padding: $euiSizeM;
- // override EuiPage
- margin-bottom: 0 !important; // sass-lint:disable-line no-important
- }
-
- .lnsWorkspacePanelWrapper__pageContentHeader--unsaved {
- color: $euiTextSubduedColor;
- }
-
.lnsWorkspacePanelWrapper__pageContentBody {
@include euiScrollBar;
flex-grow: 1;
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.test.tsx
index f7ae7753698bb..6cca42dc1cb93 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.test.tsx
@@ -36,7 +36,6 @@ describe('workspace_panel_wrapper', () => {
visualizationMap={{ myVis: mockVisualization }}
datasourceMap={{}}
datasourceStates={{}}
- emptyExpression={false}
>
@@ -58,7 +57,6 @@ describe('workspace_panel_wrapper', () => {
visualizationMap={{ myVis: { ...mockVisualization, renderToolbar: renderToolbarMock } }}
datasourceMap={{}}
datasourceStates={{}}
- emptyExpression={false}
/>
);
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx
index 5cfc269dbb97b..33ddc23312a96 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx
@@ -8,11 +8,9 @@ import './workspace_panel_wrapper.scss';
import React, { useCallback } from 'react';
import { i18n } from '@kbn/i18n';
-import classNames from 'classnames';
import {
EuiPageContent,
EuiPageContentBody,
- EuiPageContentHeader,
EuiFlexGroup,
EuiFlexItem,
EuiScreenReaderOnly,
@@ -27,7 +25,6 @@ export interface WorkspacePanelWrapperProps {
framePublicAPI: FramePublicAPI;
visualizationState: unknown;
dispatch: (action: Action) => void;
- emptyExpression: boolean;
title?: string;
visualizationMap: Record;
visualizationId: string | null;
@@ -47,7 +44,6 @@ export function WorkspacePanelWrapper({
visualizationState,
dispatch,
title,
- emptyExpression,
visualizationId,
visualizationMap,
datasourceMap,
@@ -105,26 +101,12 @@ export function WorkspacePanelWrapper({
- {!emptyExpression || title ? (
-
-
- {title ||
- i18n.translate('xpack.lens.chartTitle.unsaved', { defaultMessage: 'Unsaved' })}
-
-
- ) : (
-
-
- {title ||
- i18n.translate('xpack.lens.chartTitle.unsaved', { defaultMessage: 'Unsaved' })}
-
-
- )}
+
+
+ {title ||
+ i18n.translate('xpack.lens.chartTitle.unsaved', { defaultMessage: 'Unsaved' })}
+
+
{children}
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
index 5658f029c48ab..9f9d7fef9c7b4 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
@@ -139,6 +139,44 @@ describe('embeddable', () => {
| expression`);
});
+ it('should initialize output with deduped list of index patterns', async () => {
+ attributeService = attributeServiceMockFromSavedVis({
+ ...savedVis,
+ references: [
+ { type: 'index-pattern', id: '123', name: 'abc' },
+ { type: 'index-pattern', id: '123', name: 'def' },
+ { type: 'index-pattern', id: '456', name: 'ghi' },
+ ],
+ });
+ const embeddable = new Embeddable(
+ {
+ timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
+ attributeService,
+ expressionRenderer,
+ basePath,
+ indexPatternService: ({
+ get: (id: string) => Promise.resolve({ id }),
+ } as unknown) as IndexPatternsContract,
+ editable: true,
+ getTrigger,
+ documentToExpression: () =>
+ Promise.resolve({
+ type: 'expression',
+ chain: [
+ { type: 'function', function: 'my', arguments: {} },
+ { type: 'function', function: 'expression', arguments: {} },
+ ],
+ }),
+ },
+ {} as LensEmbeddableInput
+ );
+ await embeddable.initializeSavedVis({} as LensEmbeddableInput);
+ const outputIndexPatterns = embeddable.getOutput().indexPatterns!;
+ expect(outputIndexPatterns.length).toEqual(2);
+ expect(outputIndexPatterns[0].id).toEqual('123');
+ expect(outputIndexPatterns[1].id).toEqual('456');
+ });
+
it('should re-render if new input is pushed', async () => {
const timeRange: TimeRange = { from: 'now-15d', to: 'now' };
const query: Query = { language: 'kquery', query: '' };
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
index 02ac58328b4e0..33e5dee99081f 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
@@ -44,7 +44,7 @@ import { getEditPath, DOC_TYPE } from '../../../common';
import { IBasePath } from '../../../../../../src/core/public';
import { LensAttributeService } from '../../lens_attribute_service';
-export type LensSavedObjectAttributes = Omit;
+export type LensSavedObjectAttributes = Omit;
export type LensByValueInput = {
attributes: LensSavedObjectAttributes;
@@ -130,7 +130,15 @@ export class Embeddable
}
async initializeSavedVis(input: LensEmbeddableInput) {
- const attributes = await this.deps.attributeService.unwrapAttributes(input);
+ const attributes:
+ | LensSavedObjectAttributes
+ | false = await this.deps.attributeService.unwrapAttributes(input).catch((e: Error) => {
+ this.onFatalError(e);
+ return false;
+ });
+ if (!attributes) {
+ return;
+ }
this.savedVis = {
...attributes,
type: this.type,
@@ -251,8 +259,10 @@ export class Embeddable
if (!this.savedVis) {
return;
}
- const promises = this.savedVis.references
- .filter(({ type }) => type === 'index-pattern')
+ const promises = _.uniqBy(
+ this.savedVis.references.filter(({ type }) => type === 'index-pattern'),
+ 'id'
+ )
.map(async ({ id }) => {
try {
return await this.deps.indexPatternService.get(id);
diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts
index 5afabb9a52367..07c16665d11b4 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts
+++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts
@@ -6,34 +6,35 @@
import moment from 'moment';
import { mergeTables } from './merge_tables';
-import { Datatable } from 'src/plugins/expressions';
+import { Datatable, ExecutionContext } from 'src/plugins/expressions';
+import { LensInspectorAdapters } from './types';
describe('lens_merge_tables', () => {
- it('should produce a row with the nested table as defined', () => {
- const sampleTable1: Datatable = {
- type: 'datatable',
- columns: [
- { id: 'bucket', name: 'A', meta: { type: 'string' } },
- { id: 'count', name: 'Count', meta: { type: 'number' } },
- ],
- rows: [
- { bucket: 'a', count: 5 },
- { bucket: 'b', count: 10 },
- ],
- };
+ const sampleTable1: Datatable = {
+ type: 'datatable',
+ columns: [
+ { id: 'bucket', name: 'A', meta: { type: 'string' } },
+ { id: 'count', name: 'Count', meta: { type: 'number' } },
+ ],
+ rows: [
+ { bucket: 'a', count: 5 },
+ { bucket: 'b', count: 10 },
+ ],
+ };
- const sampleTable2: Datatable = {
- type: 'datatable',
- columns: [
- { id: 'bucket', name: 'C', meta: { type: 'string' } },
- { id: 'avg', name: 'Average', meta: { type: 'number' } },
- ],
- rows: [
- { bucket: 'a', avg: 2.5 },
- { bucket: 'b', avg: 9 },
- ],
- };
+ const sampleTable2: Datatable = {
+ type: 'datatable',
+ columns: [
+ { id: 'bucket', name: 'C', meta: { type: 'string' } },
+ { id: 'avg', name: 'Average', meta: { type: 'number' } },
+ ],
+ rows: [
+ { bucket: 'a', avg: 2.5 },
+ { bucket: 'b', avg: 9 },
+ ],
+ };
+ it('should produce a row with the nested table as defined', () => {
expect(
mergeTables.fn(
null,
@@ -47,6 +48,15 @@ describe('lens_merge_tables', () => {
});
});
+ it('should store the current tables in the tables inspector', () => {
+ const adapters: LensInspectorAdapters = { tables: {} };
+ mergeTables.fn(null, { layerIds: ['first', 'second'], tables: [sampleTable1, sampleTable2] }, {
+ inspectorAdapters: adapters,
+ } as ExecutionContext);
+ expect(adapters.tables!.first).toBe(sampleTable1);
+ expect(adapters.tables!.second).toBe(sampleTable2);
+ });
+
it('should pass the date range along', () => {
expect(
mergeTables.fn(
diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts
index e4f7b07084ea9..03ef7cf9cc637 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts
+++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts
@@ -6,6 +6,7 @@
import { i18n } from '@kbn/i18n';
import {
+ ExecutionContext,
Datatable,
ExpressionFunctionDefinition,
ExpressionValueSearchContext,
@@ -14,6 +15,7 @@ import { search } from '../../../../../src/plugins/data/public';
const { toAbsoluteDates } = search.aggs;
import { LensMultiTable } from '../types';
+import { LensInspectorAdapters } from './types';
interface MergeTables {
layerIds: string[];
@@ -24,12 +26,14 @@ export const mergeTables: ExpressionFunctionDefinition<
'lens_merge_tables',
ExpressionValueSearchContext | null,
MergeTables,
- LensMultiTable
+ LensMultiTable,
+ ExecutionContext
> = {
name: 'lens_merge_tables',
type: 'lens_multitable',
help: i18n.translate('xpack.lens.functions.mergeTables.help', {
- defaultMessage: 'A helper to merge any number of kibana tables into a single table',
+ defaultMessage:
+ 'A helper to merge any number of kibana tables into a single table and expose it via inspector adapter',
}),
args: {
layerIds: {
@@ -44,10 +48,18 @@ export const mergeTables: ExpressionFunctionDefinition<
},
},
inputTypes: ['kibana_context', 'null'],
- fn(input, { layerIds, tables }) {
+ fn(input, { layerIds, tables }, context) {
+ if (!context.inspectorAdapters) {
+ context.inspectorAdapters = {};
+ }
+ if (!context.inspectorAdapters.tables) {
+ context.inspectorAdapters.tables = {};
+ }
const resultTables: Record = {};
tables.forEach((table, index) => {
resultTables[layerIds[index]] = table;
+ // adapter is always defined at that point because we make sure by the beginning of the function
+ context.inspectorAdapters.tables![layerIds[index]] = table;
});
return {
type: 'lens_multitable',
diff --git a/x-pack/plugins/lens/public/editor_frame_service/types.ts b/x-pack/plugins/lens/public/editor_frame_service/types.ts
new file mode 100644
index 0000000000000..2da95ec2fd66f
--- /dev/null
+++ b/x-pack/plugins/lens/public/editor_frame_service/types.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Datatable } from 'src/plugins/expressions';
+
+export type TableInspectorAdapter = Record;
+export interface LensInspectorAdapters {
+ tables?: TableInspectorAdapter;
+}
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
index 829bd333ce2cc..92a4dad14dd25 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
@@ -174,6 +174,7 @@ describe('IndexPatternDimensionEditorPanel', () => {
} as unknown) as DataPublicPluginStart['fieldFormats'],
} as unknown) as DataPublicPluginStart,
core: {} as CoreSetup,
+ dimensionGroups: [],
};
jest.clearAllMocks();
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts
index bbd1d4e0ae3ab..dd696f8be357f 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts
@@ -146,6 +146,7 @@ describe('IndexPatternDimensionEditorPanel', () => {
} as unknown) as DataPublicPluginStart['fieldFormats'],
} as unknown) as DataPublicPluginStart,
core: {} as CoreSetup,
+ dimensionGroups: [],
};
jest.clearAllMocks();
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts
index 35987656f6670..92280b0fb6ce6 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts
@@ -33,19 +33,23 @@ export class IndexPatternDatasource {
{ expressions, editorFrame, charts }: IndexPatternDatasourceSetupPlugins
) {
editorFrame.registerDatasource(async () => {
- const { getIndexPatternDatasource, renameColumns, formatColumn } = await import(
- '../async_services'
- );
- expressions.registerFunction(renameColumns);
- expressions.registerFunction(formatColumn);
- return core.getStartServices().then(([coreStart, { data }]) =>
- getIndexPatternDatasource({
+ const {
+ getIndexPatternDatasource,
+ renameColumns,
+ formatColumn,
+ getTimeScaleFunction,
+ } = await import('../async_services');
+ return core.getStartServices().then(([coreStart, { data }]) => {
+ expressions.registerFunction(getTimeScaleFunction(data));
+ expressions.registerFunction(renameColumns);
+ expressions.registerFunction(formatColumn);
+ return getIndexPatternDatasource({
core: coreStart,
storage: new Storage(localStorage),
data,
charts,
- })
- ) as Promise;
+ });
+ }) as Promise;
});
}
}
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx
index 0d82292780808..fa106e90d518a 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx
@@ -76,6 +76,7 @@ export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: stri
export * from './rename_columns';
export * from './format_column';
+export * from './time_scale';
export function getIndexPatternDatasource({
core,
@@ -342,7 +343,7 @@ export function getIndexPatternDatasource({
getDatasourceSuggestionsFromCurrentState,
getDatasourceSuggestionsForVisualizeField,
- getErrorMessages(state) {
+ getErrorMessages(state, layersGroups) {
if (!state) {
return;
}
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx
index 85deb2bac25ca..dcb4646816e13 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx
@@ -34,6 +34,13 @@ export interface TermsIndexPatternColumn extends FieldBasedIndexPatternColumn {
size: number;
orderBy: { type: 'alphabetical' } | { type: 'column'; columnId: string };
orderDirection: 'asc' | 'desc';
+ // Terms on numeric fields can be formatted
+ format?: {
+ id: string;
+ params?: {
+ decimals: number;
+ };
+ };
};
}
@@ -105,10 +112,16 @@ export const termsOperation: OperationDefinition {
+ const newParams = { ...oldColumn.params };
+ if ('format' in newParams && field.type !== 'number') {
+ delete newParams.format;
+ }
return {
...oldColumn,
+ dataType: field.type as DataType,
label: ofName(field.displayName),
sourceField: field.name,
+ params: newParams,
};
},
onOtherColumnChanged: (currentColumn, columns) => {
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx
index 2c4e67ef0d9b9..1341ca0587c75 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx
@@ -103,14 +103,40 @@ describe('terms', () => {
},
};
const indexPattern = createMockedIndexPattern();
- const newDateField = indexPattern.fields.find((i) => i.name === 'dest')!;
+ const newNumberField = indexPattern.fields.find((i) => i.name === 'bytes')!;
- const column = termsOperation.onFieldChange(oldColumn, indexPattern, newDateField);
- expect(column).toHaveProperty('sourceField', 'dest');
+ const column = termsOperation.onFieldChange(oldColumn, indexPattern, newNumberField);
+ expect(column).toHaveProperty('dataType', 'number');
+ expect(column).toHaveProperty('sourceField', 'bytes');
expect(column).toHaveProperty('params.size', 5);
expect(column).toHaveProperty('params.orderBy.type', 'alphabetical');
expect(column).toHaveProperty('params.orderDirection', 'asc');
- expect(column.label).toContain('dest');
+ expect(column.label).toContain('bytes');
+ });
+
+ it('should remove numeric parameters when changing away from number', () => {
+ const oldColumn: TermsIndexPatternColumn = {
+ operationType: 'terms',
+ sourceField: 'bytes',
+ label: 'Top values of bytes',
+ isBucketed: true,
+ dataType: 'number',
+ params: {
+ size: 5,
+ orderBy: {
+ type: 'alphabetical',
+ },
+ orderDirection: 'asc',
+ format: { id: 'number', params: { decimals: 0 } },
+ },
+ };
+ const indexPattern = createMockedIndexPattern();
+ const newStringField = indexPattern.fields.find((i) => i.name === 'source')!;
+
+ const column = termsOperation.onFieldChange(oldColumn, indexPattern, newStringField);
+ expect(column).toHaveProperty('dataType', 'string');
+ expect(column).toHaveProperty('sourceField', 'source');
+ expect(column.params.format).toBeUndefined();
});
});
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts
new file mode 100644
index 0000000000000..c29e2cd9567dc
--- /dev/null
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts
@@ -0,0 +1,368 @@
+/*
+ * 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 moment from 'moment';
+import { Datatable } from 'src/plugins/expressions/public';
+import { DataPublicPluginStart } from 'src/plugins/data/public';
+import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
+import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils';
+import { getTimeScaleFunction, TimeScaleArgs } from './time_scale';
+
+describe('time_scale', () => {
+ let timeScale: (input: Datatable, args: TimeScaleArgs) => Promise;
+ let dataMock: jest.Mocked;
+
+ const emptyTable: Datatable = {
+ type: 'datatable',
+ columns: [
+ {
+ id: 'date',
+ name: 'date',
+ meta: {
+ type: 'date',
+ },
+ },
+ {
+ id: 'metric',
+ name: 'metric',
+ meta: {
+ type: 'number',
+ },
+ },
+ ],
+ rows: [],
+ };
+
+ const defaultArgs: TimeScaleArgs = {
+ dateColumnId: 'date',
+ inputColumnId: 'metric',
+ outputColumnId: 'scaledMetric',
+ targetUnit: 'h',
+ };
+
+ beforeEach(() => {
+ dataMock = dataPluginMock.createStartContract();
+ (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({
+ timeZone: 'UTC',
+ timeRange: {
+ from: '2020-10-05T00:00:00.000Z',
+ to: '2020-10-10T00:00:00.000Z',
+ },
+ interval: '1d',
+ });
+ (dataMock.query.timefilter.timefilter.calculateBounds as jest.Mock).mockImplementation(
+ ({ from, to }) => ({
+ min: moment(from),
+ max: moment(to),
+ })
+ );
+ timeScale = functionWrapper(getTimeScaleFunction(dataMock));
+ });
+
+ it('should apply time scale factor to each row', async () => {
+ const result = await timeScale(
+ {
+ ...emptyTable,
+ rows: [
+ {
+ date: moment('2020-10-05T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ {
+ date: moment('2020-10-06T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ {
+ date: moment('2020-10-07T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ {
+ date: moment('2020-10-08T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ {
+ date: moment('2020-10-09T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ ],
+ },
+ {
+ ...defaultArgs,
+ }
+ );
+
+ expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1, 1]);
+ });
+
+ it('should skip gaps in the data', async () => {
+ const result = await timeScale(
+ {
+ ...emptyTable,
+ rows: [
+ {
+ date: moment('2020-10-05T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ {
+ date: moment('2020-10-06T00:00:00.000Z').valueOf(),
+ },
+ {
+ date: moment('2020-10-07T00:00:00.000Z').valueOf(),
+ },
+ {
+ date: moment('2020-10-08T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ {
+ date: moment('2020-10-09T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ ],
+ },
+ {
+ ...defaultArgs,
+ }
+ );
+
+ expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([
+ 1,
+ undefined,
+ undefined,
+ 1,
+ 1,
+ ]);
+ });
+
+ it('should return input unchanged if input column does not exist', async () => {
+ const mismatchedTable = {
+ ...emptyTable,
+ rows: [
+ {
+ date: moment('2020-10-05T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ ],
+ };
+ const result = await timeScale(mismatchedTable, {
+ ...defaultArgs,
+ inputColumnId: 'nonexistent',
+ });
+
+ expect(result).toBe(mismatchedTable);
+ });
+
+ it('should be able to scale up as well', async () => {
+ (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({
+ timeZone: 'UTC',
+ timeRange: {
+ from: '2020-10-05T12:00:00.000Z',
+ to: '2020-10-05T16:00:00.000Z',
+ },
+ interval: '1h',
+ });
+ const result = await timeScale(
+ {
+ ...emptyTable,
+ rows: [
+ {
+ date: moment('2020-10-05T12:00:00.000Z').valueOf(),
+ metric: 1,
+ },
+ {
+ date: moment('2020-10-05T13:00:00.000Z').valueOf(),
+ metric: 1,
+ },
+ {
+ date: moment('2020-10-05T14:00:00.000Z').valueOf(),
+ metric: 1,
+ },
+ {
+ date: moment('2020-10-05T15:00:00.000Z').valueOf(),
+ metric: 1,
+ },
+ ],
+ },
+ {
+ ...defaultArgs,
+ targetUnit: 'd',
+ }
+ );
+
+ expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([24, 24, 24, 24]);
+ });
+
+ it('can scale starting from unit multiple target intervals', async () => {
+ (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({
+ timeZone: 'UTC',
+ timeRange: {
+ from: '2020-10-05T13:00:00.000Z',
+ to: '2020-10-05T23:00:00.000Z',
+ },
+ interval: '3h',
+ });
+ const result = await timeScale(
+ {
+ ...emptyTable,
+ rows: [
+ {
+ // bucket is cut off by one hour because of the time range
+ date: moment('2020-10-05T12:00:00.000Z').valueOf(),
+ metric: 2,
+ },
+ {
+ date: moment('2020-10-05T15:00:00.000Z').valueOf(),
+ metric: 3,
+ },
+ {
+ date: moment('2020-10-05T18:00:00.000Z').valueOf(),
+ metric: 3,
+ },
+ {
+ // bucket is cut off by one hour because of the time range
+ date: moment('2020-10-05T21:00:00.000Z').valueOf(),
+ metric: 2,
+ },
+ ],
+ },
+ {
+ ...defaultArgs,
+ targetUnit: 'h',
+ }
+ );
+
+ expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1]);
+ });
+
+ it('take start and end of timerange into account', async () => {
+ (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({
+ timeZone: 'UTC',
+ timeRange: {
+ from: '2020-10-05T12:00:00.000Z',
+ to: '2020-10-09T12:00:00.000Z',
+ },
+ interval: '1d',
+ });
+ const result = await timeScale(
+ {
+ ...emptyTable,
+ rows: [
+ {
+ // this is a partial bucket because it starts before the start of the time range
+ date: moment('2020-10-05T00:00:00.000Z').valueOf(),
+ metric: 12,
+ },
+ {
+ date: moment('2020-10-06T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ {
+ date: moment('2020-10-07T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ {
+ date: moment('2020-10-08T00:00:00.000Z').valueOf(),
+ metric: 24,
+ },
+ {
+ // this is a partial bucket because it ends earlier than the regular interval of 1d
+ date: moment('2020-10-09T00:00:00.000Z').valueOf(),
+ metric: 12,
+ },
+ ],
+ },
+ {
+ ...defaultArgs,
+ }
+ );
+
+ expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1, 1]);
+ });
+
+ it('should respect DST switches', async () => {
+ (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({
+ timeZone: 'Europe/Berlin',
+ timeRange: {
+ from: '2020-10-23T00:00:00.000+02:00',
+ to: '2020-10-27T00:00:00.000+01:00',
+ },
+ interval: '1d',
+ });
+ const result = await timeScale(
+ {
+ ...emptyTable,
+ rows: [
+ {
+ date: moment('2020-10-23T00:00:00.000+02:00').valueOf(),
+ metric: 24,
+ },
+ {
+ date: moment('2020-10-24T00:00:00.000+02:00').valueOf(),
+ metric: 24,
+ },
+ {
+ // this day has one hour more in Europe/Berlin due to DST switch
+ date: moment('2020-10-25T00:00:00.000+02:00').valueOf(),
+ metric: 25,
+ },
+ {
+ date: moment('2020-10-26T00:00:00.000+01:00').valueOf(),
+ metric: 24,
+ },
+ ],
+ },
+ {
+ ...defaultArgs,
+ }
+ );
+
+ expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1]);
+ });
+
+ it('take leap years into account', async () => {
+ (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({
+ timeZone: 'UTC',
+ timeRange: {
+ from: '2010-01-01T00:00:00.000Z',
+ to: '2015-01-01T00:00:00.000Z',
+ },
+ interval: '1y',
+ });
+ const result = await timeScale(
+ {
+ ...emptyTable,
+ rows: [
+ {
+ date: moment('2010-01-01T00:00:00.000Z').valueOf(),
+ metric: 365,
+ },
+ {
+ date: moment('2011-01-01T00:00:00.000Z').valueOf(),
+ metric: 365,
+ },
+ {
+ // 2012 is a leap year and has an additional day
+ date: moment('2012-01-01T00:00:00.000Z').valueOf(),
+ metric: 366,
+ },
+ {
+ date: moment('2013-01-01T00:00:00.000Z').valueOf(),
+ metric: 365,
+ },
+ {
+ date: moment('2014-01-01T00:00:00.000Z').valueOf(),
+ metric: 365,
+ },
+ ],
+ },
+ {
+ ...defaultArgs,
+ targetUnit: 'd',
+ }
+ );
+
+ expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1, 1]);
+ });
+});
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts
new file mode 100644
index 0000000000000..0937f40eeb6d3
--- /dev/null
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts
@@ -0,0 +1,167 @@
+/*
+ * 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 moment from 'moment-timezone';
+import { i18n } from '@kbn/i18n';
+import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public';
+import { DataPublicPluginStart } from 'src/plugins/data/public';
+import { search } from '../../../../../src/plugins/data/public';
+
+type TimeScaleUnit = 's' | 'm' | 'h' | 'd';
+
+export interface TimeScaleArgs {
+ dateColumnId: string;
+ inputColumnId: string;
+ outputColumnId: string;
+ targetUnit: TimeScaleUnit;
+ outputColumnName?: string;
+}
+
+const unitInMs: Record = {
+ s: 1000,
+ m: 1000 * 60,
+ h: 1000 * 60 * 60,
+ d: 1000 * 60 * 60 * 24,
+};
+
+export function getTimeScaleFunction(data: DataPublicPluginStart) {
+ const timeScale: ExpressionFunctionDefinition<
+ 'lens_time_scale',
+ Datatable,
+ TimeScaleArgs,
+ Promise
+ > = {
+ name: 'lens_time_scale',
+ type: 'datatable',
+ help: '',
+ args: {
+ dateColumnId: {
+ types: ['string'],
+ help: '',
+ required: true,
+ },
+ inputColumnId: {
+ types: ['string'],
+ help: '',
+ required: true,
+ },
+ outputColumnId: {
+ types: ['string'],
+ help: '',
+ required: true,
+ },
+ outputColumnName: {
+ types: ['string'],
+ help: '',
+ },
+ targetUnit: {
+ types: ['string'],
+ options: ['s', 'm', 'h', 'd'],
+ help: '',
+ required: true,
+ },
+ },
+ inputTypes: ['datatable'],
+ async fn(
+ input,
+ { dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs
+ ) {
+ if (input.columns.some((column) => column.id === outputColumnId)) {
+ throw new Error(
+ i18n.translate('xpack.lens.functions.timeScale.columnConflictMessage', {
+ defaultMessage: 'Specified outputColumnId {columnId} already exists.',
+ values: {
+ columnId: outputColumnId,
+ },
+ })
+ );
+ }
+
+ const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId);
+
+ if (!dateColumnDefinition) {
+ throw new Error(
+ i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', {
+ defaultMessage: 'Specified dateColumnId {columnId} does not exist.',
+ values: {
+ columnId: dateColumnId,
+ },
+ })
+ );
+ }
+
+ const inputColumnDefinition = input.columns.find((column) => column.id === inputColumnId);
+
+ if (!inputColumnDefinition) {
+ return input;
+ }
+
+ const outputColumnDefinition = {
+ ...inputColumnDefinition,
+ id: outputColumnId,
+ name: outputColumnName || outputColumnId,
+ };
+
+ const resultColumns = [...input.columns];
+ // add output column after input column in the table
+ resultColumns.splice(
+ resultColumns.indexOf(inputColumnDefinition) + 1,
+ 0,
+ outputColumnDefinition
+ );
+
+ const targetUnitInMs = unitInMs[targetUnit];
+ const timeInfo = await data.search.aggs.getDateMetaByDatatableColumn(dateColumnDefinition);
+ const intervalDuration = timeInfo && search.aggs.parseInterval(timeInfo.interval);
+
+ if (!timeInfo || !intervalDuration) {
+ throw new Error(
+ i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', {
+ defaultMessage: 'Could not fetch date histogram information',
+ })
+ );
+ }
+ // the datemath plugin always parses dates by using the current default moment time zone.
+ // to use the configured time zone, we are switching just for the bounds calculation.
+ const defaultTimezone = moment().zoneName();
+ moment.tz.setDefault(timeInfo.timeZone);
+
+ const timeBounds =
+ timeInfo.timeRange && data.query.timefilter.timefilter.calculateBounds(timeInfo.timeRange);
+
+ const result = {
+ ...input,
+ columns: resultColumns,
+ rows: input.rows.map((row) => {
+ const newRow = { ...row };
+
+ let startOfBucket = moment(row[dateColumnId]);
+ let endOfBucket = startOfBucket.clone().add(intervalDuration);
+ if (timeBounds && timeBounds.min) {
+ startOfBucket = moment.max(startOfBucket, timeBounds.min);
+ }
+ if (timeBounds && timeBounds.max) {
+ endOfBucket = moment.min(endOfBucket, timeBounds.max);
+ }
+ const bucketSize = endOfBucket.diff(startOfBucket);
+ const factor = bucketSize / targetUnitInMs;
+
+ const currentValue = newRow[inputColumnId];
+ if (currentValue != null) {
+ newRow[outputColumnId] = Number(currentValue) / factor;
+ }
+
+ return newRow;
+ }),
+ };
+ // reset default moment timezone
+ moment.tz.setDefault(defaultTimezone);
+
+ return result;
+ },
+ };
+ return timeScale;
+}
diff --git a/x-pack/plugins/lens/public/lens_attribute_service.ts b/x-pack/plugins/lens/public/lens_attribute_service.ts
index fac8f445abb9e..e8bb031a2b027 100644
--- a/x-pack/plugins/lens/public/lens_attribute_service.ts
+++ b/x-pack/plugins/lens/public/lens_attribute_service.ts
@@ -12,7 +12,7 @@ import {
LensByValueInput,
LensByReferenceInput,
} from './editor_frame_service/embeddable/embeddable';
-import { SavedObjectIndexStore } from './persistence';
+import { SavedObjectIndexStore, Document } from './persistence';
import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/saved_objects/public';
import { DOC_TYPE } from '../common';
@@ -22,6 +22,12 @@ export type LensAttributeService = AttributeService<
LensByReferenceInput
>;
+function documentToAttributes(doc: Document): LensSavedObjectAttributes {
+ delete doc.savedObjectId;
+ delete doc.type;
+ return { ...doc };
+}
+
export function getLensAttributeService(
core: CoreStart,
startDependencies: LensPluginStartDependencies
@@ -41,14 +47,8 @@ export function getLensAttributeService(
return { id: savedDoc.savedObjectId };
},
unwrapMethod: async (savedObjectId: string): Promise => {
- const savedObject = await core.savedObjects.client.get(
- DOC_TYPE,
- savedObjectId
- );
- return {
- ...savedObject.attributes,
- references: savedObject.references,
- };
+ const attributes = documentToAttributes(await savedObjectStore.load(savedObjectId));
+ return attributes;
},
checkForDuplicateTitle: (props: OnSaveProps) => {
const savedObjectsClient = core.savedObjects.client;
diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts
index 36237eeb6b05f..2f9310ee24ae9 100644
--- a/x-pack/plugins/lens/public/plugin.ts
+++ b/x-pack/plugins/lens/public/plugin.ts
@@ -9,7 +9,7 @@ import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/p
import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public';
import { DashboardStart } from 'src/plugins/dashboard/public';
import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public';
-import { VisualizationsSetup } from 'src/plugins/visualizations/public';
+import { VisualizationsSetup, VisualizationsStart } from 'src/plugins/visualizations/public';
import { NavigationPublicPluginStart } from 'src/plugins/navigation/public';
import { UrlForwardingSetup } from 'src/plugins/url_forwarding/public';
import { GlobalSearchPluginSetup } from '../../global_search/public';
@@ -35,6 +35,7 @@ import {
VISUALIZE_FIELD_TRIGGER,
} from '../../../../src/plugins/ui_actions/public';
import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common';
+import { PLUGIN_ID_OSS } from '../../../../src/plugins/lens_oss/common/constants';
import { EditorFrameStart } from './types';
import { getLensAliasConfig } from './vis_type_alias';
import { visualizeFieldAction } from './trigger_actions/visualize_field_actions';
@@ -58,6 +59,7 @@ export interface LensPluginStartDependencies {
navigation: NavigationPublicPluginStart;
uiActions: UiActionsStart;
dashboard: DashboardStart;
+ visualizations: VisualizationsStart;
embeddable: EmbeddableStart;
charts: ChartsPluginStart;
savedObjectsTagging?: SavedObjectTaggingPluginStart;
@@ -170,6 +172,8 @@ export class LensPlugin {
start(core: CoreStart, startDependencies: LensPluginStartDependencies) {
this.createEditorFrame = this.editorFrameService.start(core, startDependencies).createInstance;
+ // unregisters the OSS alias
+ startDependencies.visualizations.unRegisterAlias(PLUGIN_ID_OSS);
// unregisters the Visualize action and registers the lens one
if (startDependencies.uiActions.hasAction(ACTION_VISUALIZE_FIELD)) {
startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD);
diff --git a/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx b/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx
index 679f3a44bb60e..20837424dc7b5 100644
--- a/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx
+++ b/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx
@@ -11,7 +11,7 @@ import { EuiIconLegend } from '../assets/legend';
const typeToIconMap: { [type: string]: string | IconType } = {
legend: EuiIconLegend as IconType,
- values: 'visText',
+ values: 'number',
};
export interface ToolbarPopoverProps {
diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts
index 27ab8f258bba8..4ad849c5d441e 100644
--- a/x-pack/plugins/lens/public/types.ts
+++ b/x-pack/plugins/lens/public/types.ts
@@ -178,10 +178,16 @@ export interface Datasource {
indexPatternId: string,
fieldName: string
) => Array>;
- getDatasourceSuggestionsFromCurrentState: (state: T) => Array>;
+ getDatasourceSuggestionsFromCurrentState: (
+ state: T,
+ activeData?: Record
+ ) => Array>;
getPublicAPI: (props: PublicAPIProps) => DatasourcePublicAPI;
- getErrorMessages: (state: T) => Array<{ shortMessage: string; longMessage: string }> | undefined;
+ getErrorMessages: (
+ state: T,
+ layersGroups?: Record
+ ) => Array<{ shortMessage: string; longMessage: string }> | undefined;
/**
* uniqueLabels of dimensions exposed for aria-labels of dragged dimensions
*/
@@ -231,6 +237,7 @@ export type DatasourceDimensionProps = SharedDimensionProps & {
columnId: string;
onRemove?: (accessor: string) => void;
state: T;
+ activeData?: Record;
};
// The only way a visualization has to restrict the query building
@@ -238,6 +245,7 @@ export type DatasourceDimensionEditorProps = DatasourceDimensionPro
setState: StateSetter;
core: Pick;
dateRange: DateRange;
+ dimensionGroups: VisualizationDimensionGroupConfig[];
};
export type DatasourceDimensionTriggerProps = DatasourceDimensionProps & {
@@ -249,6 +257,7 @@ export interface DatasourceLayerPanelProps {
layerId: string;
state: T;
setState: StateSetter;
+ activeData?: Record;
}
export interface DraggedOperation {
@@ -428,6 +437,12 @@ export interface VisualizationSuggestion {
export interface FramePublicAPI {
datasourceLayers: Record;
+ /**
+ * Data of the chart currently rendered in the preview.
+ * This data might be not available (e.g. if the chart can't be rendered) or outdated and belonging to another chart.
+ * If accessing, make sure to check whether expected columns actually exist.
+ */
+ activeData?: Record;
dateRange: DateRange;
query: Query;
diff --git a/x-pack/plugins/lens/public/vis_type_alias.ts b/x-pack/plugins/lens/public/vis_type_alias.ts
index d0dceed03db2f..e20dcfacd5ab5 100644
--- a/x-pack/plugins/lens/public/vis_type_alias.ts
+++ b/x-pack/plugins/lens/public/vis_type_alias.ts
@@ -12,19 +12,16 @@ export const getLensAliasConfig = (): VisTypeAlias => ({
aliasPath: getBasePath(),
aliasApp: 'lens',
name: 'lens',
- promotion: {
- description: i18n.translate('xpack.lens.visTypeAlias.promotion.description', {
- defaultMessage: 'Try Lens, our new, intuitive way to create visualizations.',
- }),
- buttonText: i18n.translate('xpack.lens.visTypeAlias.promotion.buttonText', {
- defaultMessage: 'Go to Lens',
- }),
- },
+ promotion: true,
title: i18n.translate('xpack.lens.visTypeAlias.title', {
defaultMessage: 'Lens',
}),
description: i18n.translate('xpack.lens.visTypeAlias.description', {
- defaultMessage: `Lens is a simpler way to create basic visualizations`,
+ defaultMessage:
+ 'Create visualizations with our drag and drop editor. Switch between visualization types at any time.',
+ }),
+ note: i18n.translate('xpack.lens.visTypeAlias.note', {
+ defaultMessage: 'Recommended for most users.',
}),
icon: 'lensApp',
stage: 'production',
diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap
index ca6ca9b2722fd..365bf8f4d6328 100644
--- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap
+++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap
@@ -279,6 +279,15 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
},
]
}
+ displayValueSettings={
+ Object {
+ "hideClippedValue": true,
+ "isAlternatingValueLabel": false,
+ "isValueContainedInElement": true,
+ "showValueLabel": false,
+ "valueFormatter": [Function],
+ }
+ }
enableHistogramMode={false}
groupId="left"
id="d-a"
@@ -334,6 +343,15 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
},
]
}
+ displayValueSettings={
+ Object {
+ "hideClippedValue": true,
+ "isAlternatingValueLabel": false,
+ "isValueContainedInElement": true,
+ "showValueLabel": false,
+ "valueFormatter": [Function],
+ }
+ }
enableHistogramMode={false}
groupId="left"
id="d-b"
@@ -457,6 +475,15 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
},
]
}
+ displayValueSettings={
+ Object {
+ "hideClippedValue": true,
+ "isAlternatingValueLabel": false,
+ "isValueContainedInElement": true,
+ "showValueLabel": false,
+ "valueFormatter": [Function],
+ }
+ }
enableHistogramMode={false}
groupId="left"
id="d-a"
@@ -512,6 +539,15 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
},
]
}
+ displayValueSettings={
+ Object {
+ "hideClippedValue": true,
+ "isAlternatingValueLabel": false,
+ "isValueContainedInElement": true,
+ "showValueLabel": false,
+ "valueFormatter": [Function],
+ }
+ }
enableHistogramMode={false}
groupId="left"
id="d-b"
@@ -1019,6 +1055,15 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
},
]
}
+ displayValueSettings={
+ Object {
+ "hideClippedValue": true,
+ "isAlternatingValueLabel": false,
+ "isValueContainedInElement": true,
+ "showValueLabel": false,
+ "valueFormatter": [Function],
+ }
+ }
enableHistogramMode={false}
groupId="left"
id="d-a"
@@ -1078,6 +1123,15 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
},
]
}
+ displayValueSettings={
+ Object {
+ "hideClippedValue": true,
+ "isAlternatingValueLabel": false,
+ "isValueContainedInElement": true,
+ "showValueLabel": false,
+ "valueFormatter": [Function],
+ }
+ }
enableHistogramMode={false}
groupId="left"
id="d-b"
@@ -1205,6 +1259,15 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
},
]
}
+ displayValueSettings={
+ Object {
+ "hideClippedValue": true,
+ "isAlternatingValueLabel": false,
+ "isValueContainedInElement": true,
+ "showValueLabel": false,
+ "valueFormatter": [Function],
+ }
+ }
enableHistogramMode={false}
groupId="left"
id="d-a"
@@ -1264,6 +1327,15 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
},
]
}
+ displayValueSettings={
+ Object {
+ "hideClippedValue": true,
+ "isAlternatingValueLabel": false,
+ "isValueContainedInElement": true,
+ "showValueLabel": false,
+ "valueFormatter": [Function],
+ }
+ }
enableHistogramMode={false}
groupId="left"
id="d-b"
diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap
index b35f915336eee..982f513ae1019 100644
--- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap
+++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap
@@ -145,6 +145,9 @@ Object {
"title": Array [
"",
],
+ "valueLabels": Array [
+ "hide",
+ ],
"xTitle": Array [
"",
],
diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
index 6c9669dc239ea..a5d292fdf265a 100644
--- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
@@ -256,6 +256,7 @@ const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({
isVisible: false,
position: Position.Top,
},
+ valueLabels: 'hide',
axisTitlesVisibilitySettings: {
type: 'lens_xy_axisTitlesVisibilityConfig',
x: true,
@@ -1867,6 +1868,7 @@ describe('xy_expression', () => {
yTitle: '',
yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
+ valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
@@ -1952,6 +1954,7 @@ describe('xy_expression', () => {
yTitle: '',
yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
+ valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
@@ -2023,6 +2026,7 @@ describe('xy_expression', () => {
yTitle: '',
yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top },
+ valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx
index 877ddd3c0f27d..d238e052a7c7f 100644
--- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx
@@ -20,6 +20,10 @@ import {
GeometryValue,
XYChartSeriesIdentifier,
StackMode,
+ RecursivePartial,
+ Theme,
+ VerticalAlignment,
+ HorizontalAlignment,
} from '@elastic/charts';
import { I18nProvider } from '@kbn/i18n/react';
import {
@@ -131,6 +135,11 @@ export const xyChart: ExpressionFunctionDefinition<
defaultMessage: 'Define how missing values are treated',
}),
},
+ valueLabels: {
+ types: ['string'],
+ options: ['hide', 'inside'],
+ help: '',
+ },
tickLabelsVisibilitySettings: {
types: ['lens_xy_tickLabelsConfig'],
help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', {
@@ -214,6 +223,40 @@ export const getXyChartRenderer = (dependencies: {
},
});
+function mergeThemeWithValueLabelsStyling(
+ theme: RecursivePartial,
+ valuesLabelMode: string = 'hide',
+ isHorizontal: boolean
+) {
+ const VALUE_LABELS_MAX_FONTSIZE = 15;
+ const VALUE_LABELS_MIN_FONTSIZE = 10;
+ const VALUE_LABELS_VERTICAL_OFFSET = -10;
+ const VALUE_LABELS_HORIZONTAL_OFFSET = 10;
+
+ if (valuesLabelMode === 'hide') {
+ return theme;
+ }
+ return {
+ ...theme,
+ ...{
+ barSeriesStyle: {
+ ...theme.barSeriesStyle,
+ displayValue: {
+ fontSize: { min: VALUE_LABELS_MIN_FONTSIZE, max: VALUE_LABELS_MAX_FONTSIZE },
+ fill: { textInverted: true, textBorder: 2 },
+ alignment: isHorizontal
+ ? {
+ vertical: VerticalAlignment.Middle,
+ }
+ : { horizontal: HorizontalAlignment.Center },
+ offsetX: isHorizontal ? VALUE_LABELS_HORIZONTAL_OFFSET : 0,
+ offsetY: isHorizontal ? 0 : VALUE_LABELS_VERTICAL_OFFSET,
+ },
+ },
+ },
+ };
+}
+
function getIconForSeriesType(seriesType: SeriesType): IconType {
return visualizationTypes.find((c) => c.id === seriesType)!.icon || 'empty';
}
@@ -254,7 +297,7 @@ export function XYChart({
onClickValue,
onSelectRange,
}: XYChartRenderProps) {
- const { legend, layers, fittingFunction, gridlinesVisibilitySettings } = args;
+ const { legend, layers, fittingFunction, gridlinesVisibilitySettings, valueLabels } = args;
const chartTheme = chartsThemeService.useChartsTheme();
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
@@ -396,6 +439,16 @@ export function XYChart({
return style;
};
+ const shouldShowValueLabels =
+ // No stacked bar charts
+ filteredLayers.every((layer) => !layer.seriesType.includes('stacked')) &&
+ // No histogram charts
+ !isHistogramViz;
+
+ const baseThemeWithMaybeValueLabels = !shouldShowValueLabels
+ ? chartTheme
+ : mergeThemeWithValueLabelsStyling(chartTheme, valueLabels, shouldRotate);
+
const colorAssignments = getColorAssignments(args.layers, data, formatFactory);
return (
@@ -408,7 +461,7 @@ export function XYChart({
}
legendPosition={legend.position}
showLegendExtra={false}
- theme={chartTheme}
+ theme={baseThemeWithMaybeValueLabels}
baseTheme={chartBaseTheme}
tooltip={{
headerFormatter: (d) => safeXAccessorLabelRenderer(d.value),
@@ -613,6 +666,10 @@ export function XYChart({
});
}
+ const yAxis = yAxesConfiguration.find((axisConfiguration) =>
+ axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor)
+ );
+
const seriesProps: SeriesSpec = {
splitSeriesAccessors: splitAccessor ? [splitAccessor] : [],
stackAccessors: seriesType.includes('stacked') ? [xAccessor as string] : [],
@@ -649,9 +706,7 @@ export function XYChart({
palette.params
);
},
- groupId: yAxesConfiguration.find((axisConfiguration) =>
- axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor)
- )?.groupId,
+ groupId: yAxis?.groupId,
enableHistogramMode:
isHistogram &&
(seriesType.includes('stacked') || !splitAccessor) &&
@@ -723,7 +778,19 @@ export function XYChart({
case 'bar_horizontal':
case 'bar_horizontal_stacked':
case 'bar_horizontal_percentage_stacked':
- return ;
+ const valueLabelsSettings = {
+ displayValueSettings: {
+ // This format double fixes two issues in elastic-chart
+ // * when rotating the chart, the formatter is not correctly picked
+ // * in some scenarios value labels are not strings, and this breaks the elastic-chart lib
+ valueFormatter: (d: unknown) => yAxis?.formatter?.convert(d) || '',
+ showValueLabel: shouldShowValueLabels && valueLabels !== 'hide',
+ isAlternatingValueLabel: false,
+ isValueContainedInElement: true,
+ hideClippedValue: true,
+ },
+ };
+ return ;
case 'area_stacked':
case 'area_percentage_stacked':
return (
diff --git a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts
index 41d18e5199e4c..bf4ffaa36a870 100644
--- a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts
@@ -5,7 +5,8 @@
*/
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
-import { SeriesType, visualizationTypes, LayerConfig, YConfig } from './types';
+import { FramePublicAPI } from '../types';
+import { SeriesType, visualizationTypes, LayerConfig, YConfig, ValidLayer } from './types';
export function isHorizontalSeries(seriesType: SeriesType) {
return (
@@ -37,3 +38,23 @@ export const getSeriesColor = (layer: LayerConfig, accessor: string) => {
layer?.yConfig?.find((yConfig: YConfig) => yConfig.forAccessor === accessor)?.color || null
);
};
+
+export function hasHistogramSeries(
+ layers: ValidLayer[] = [],
+ datasourceLayers?: FramePublicAPI['datasourceLayers']
+) {
+ if (!datasourceLayers) {
+ return false;
+ }
+ const validLayers = layers.filter(({ accessors }) => accessors.length);
+
+ return validLayers.some(({ layerId, xAccessor }: ValidLayer) => {
+ const xAxisOperation = datasourceLayers[layerId].getOperationForColumnId(xAccessor);
+ return (
+ xAxisOperation &&
+ xAxisOperation.isBucketed &&
+ xAxisOperation.scale &&
+ xAxisOperation.scale !== 'ordinal'
+ );
+ });
+}
diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts
index 6148824bfec21..05a4b7f460adb 100644
--- a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts
@@ -43,6 +43,7 @@ describe('#toExpression', () => {
xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'Carry',
tickLabelsVisibilitySettings: { x: false, yLeft: true, yRight: true },
@@ -67,6 +68,7 @@ describe('#toExpression', () => {
(xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -87,6 +89,7 @@ describe('#toExpression', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -113,6 +116,7 @@ describe('#toExpression', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -136,6 +140,7 @@ describe('#toExpression', () => {
xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -156,6 +161,7 @@ describe('#toExpression', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -191,6 +197,7 @@ describe('#toExpression', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -217,6 +224,7 @@ describe('#toExpression', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -238,4 +246,25 @@ describe('#toExpression', () => {
yRight: [true],
});
});
+
+ it('should correctly report the valueLabels visibility settings', () => {
+ const expression = xyVisualization.toExpression(
+ {
+ legend: { position: Position.Bottom, isVisible: true },
+ valueLabels: 'inside',
+ preferredSeriesType: 'bar',
+ layers: [
+ {
+ layerId: 'first',
+ seriesType: 'area',
+ splitAccessor: 'd',
+ xAccessor: 'a',
+ accessors: ['b', 'c'],
+ },
+ ],
+ },
+ frame.datasourceLayers
+ ) as Ast;
+ expect(expression.chain[0].arguments.valueLabels[0] as Ast).toEqual('inside');
+ });
});
diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts
index 904d1541a85ec..df773146cde4d 100644
--- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts
@@ -7,13 +7,9 @@
import { Ast } from '@kbn/interpreter/common';
import { ScaleType } from '@elastic/charts';
import { PaletteRegistry } from 'src/plugins/charts/public';
-import { State, LayerConfig } from './types';
+import { State, ValidLayer, LayerConfig } from './types';
import { OperationMetadata, DatasourcePublicAPI } from '../types';
-interface ValidLayer extends LayerConfig {
- xAccessor: NonNullable;
-}
-
export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: LayerConfig) => {
const originalOrder = datasource
.getTableSpec()
@@ -60,6 +56,7 @@ export function toPreviewExpression(
...state.legend,
isVisible: false,
},
+ valueLabels: 'hide',
},
datasourceLayers,
paletteService,
@@ -197,6 +194,7 @@ export const buildExpression = (
],
},
],
+ valueLabels: [state?.valueLabels || 'hide'],
layers: validLayers.map((layer) => {
const columnToLabel: Record = {};
diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts
index d1e78aec57998..d21ac675d0745 100644
--- a/x-pack/plugins/lens/public/xy_visualization/types.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/types.ts
@@ -364,6 +364,8 @@ export type SeriesType =
export type YAxisMode = 'auto' | 'left' | 'right';
+export type ValueLabelConfig = 'hide' | 'inside' | 'outside';
+
export interface YConfig {
forAccessor: string;
axisMode?: YAxisMode;
@@ -381,6 +383,10 @@ export interface LayerConfig {
palette?: PaletteOutput;
}
+export interface ValidLayer extends LayerConfig {
+ xAccessor: NonNullable;
+}
+
export type LayerArgs = LayerConfig & {
columnToLabel?: string; // Actually a JSON key-value pair
yScaleType: 'time' | 'linear' | 'log' | 'sqrt';
@@ -398,6 +404,7 @@ export interface XYArgs {
yTitle: string;
yRightTitle: string;
legend: LegendConfig & { type: 'lens_xy_legendConfig' };
+ valueLabels: ValueLabelConfig;
layers: LayerArgs[];
fittingFunction?: FittingFunction;
axisTitlesVisibilitySettings?: AxesSettingsConfig & {
@@ -411,6 +418,7 @@ export interface XYArgs {
export interface XYState {
preferredSeriesType: SeriesType;
legend: LegendConfig;
+ valueLabels?: ValueLabelConfig;
fittingFunction?: FittingFunction;
layers: LayerConfig[];
xTitle?: string;
diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts
index 7c49afa53af3e..5127e5c2c2597 100644
--- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts
@@ -15,6 +15,7 @@ import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'
function exampleState(): State {
return {
legend: { position: Position.Bottom, isVisible: true },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -150,6 +151,7 @@ describe('xy_visualization', () => {
},
"preferredSeriesType": "bar_stacked",
"title": "Empty XY chart",
+ "valueLabels": "hide",
}
`);
});
diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx
index c7f775586ca0d..7e155de14a39a 100644
--- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx
@@ -145,6 +145,7 @@ export const getXyVisualization = ({
state || {
title: 'Empty XY chart',
legend: { isVisible: true, position: Position.Right },
+ valueLabels: 'hide',
preferredSeriesType: defaultSeriesType,
layers: [
{
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss
index 5b14fca78e65d..b9ff6a56d8e35 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss
@@ -1,3 +1,3 @@
.lnsXyToolbar__popover {
width: 320px;
-}
\ No newline at end of file
+}
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx
index 2114d63fcfacd..721bff8684a19 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx
@@ -21,6 +21,7 @@ describe('XY Config panels', () => {
function testState(): State {
return {
legend: { isVisible: true, position: Position.Right },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -115,8 +116,9 @@ describe('XY Config panels', () => {
expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry');
});
- it('should disable the popover if there is no area or line series', () => {
+ it('should show currently selected value labels display setting', () => {
const state = testState();
+
const component = shallow(
{
...state,
layers: [{ ...state.layers[0], seriesType: 'bar' }],
fittingFunction: 'Carry',
+ valueLabels: 'inside',
+ }}
+ />
+ );
+
+ expect(component.find(EuiButtonGroup).prop('idSelected')).toEqual('value_labels_inside');
+ });
+
+ it('should disable the popover for stacked bar charts', () => {
+ const state = testState();
+ const component = shallow(
+
+ );
+
+ expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true);
+ });
+
+ it('should disable the popover for percentage area charts', () => {
+ const state = testState();
+ const component = shallow(
+
+ );
+
+ expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true);
+ });
+
+ it('should disabled the popover if there is histogram series', () => {
+ // make it detect an histogram series
+ frame.datasourceLayers.first.getOperationForColumnId = jest.fn().mockReturnValueOnce({
+ isBucketed: true,
+ scale: 'interval',
+ });
+ const state = testState();
+ const component = shallow(
+
+ );
+
+ expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true);
+ });
+
+ it('should show the popover and display field enabled for bar and horizontal_bar series', () => {
+ const state = testState();
+
+ const component = shallow(
+
+ );
+
+ expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true);
+ });
+
+ it('should hide the fitting option for bar series', () => {
+ const state = testState();
+ const component = shallow(
+
+ );
+
+ expect(component.exists('[data-test-subj="lnsMissingValuesSelect"]')).toEqual(false);
+ });
+
+ it('should hide in the popover the display option for area and line series', () => {
+ const state = testState();
+ const component = shallow(
+
+ );
+
+ expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(false);
+ });
+
+ it('should keep the display option for bar series with multiple layers', () => {
+ frame.datasourceLayers = {
+ ...frame.datasourceLayers,
+ second: createMockDatasource('test').publicAPIMock,
+ };
+
+ const state = testState();
+ const component = shallow(
+
);
- expect(component.find(ToolbarPopover).at(0).prop('isDisabled')).toEqual(true);
+ expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true);
});
it('should disable the popover if there is no right axis', () => {
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
index 97e42113fc180..a22530c5743b4 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
@@ -27,8 +27,20 @@ import {
VisualizationToolbarProps,
VisualizationDimensionEditorProps,
} from '../types';
-import { State, SeriesType, visualizationTypes, YAxisMode, AxesSettingsConfig } from './types';
-import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers';
+import {
+ State,
+ SeriesType,
+ visualizationTypes,
+ YAxisMode,
+ AxesSettingsConfig,
+ ValidLayer,
+} from './types';
+import {
+ isHorizontalChart,
+ isHorizontalSeries,
+ getSeriesColor,
+ hasHistogramSeries,
+} from './state_helpers';
import { trackUiEvent } from '../lens_ui_telemetry';
import { fittingFunctionDefinitions } from './fitting_functions';
import { ToolbarPopover, LegendSettingsPopover } from '../shared_components';
@@ -74,6 +86,27 @@ const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label:
},
];
+const valueLabelsOptions: Array<{
+ id: string;
+ value: 'hide' | 'inside' | 'outside';
+ label: string;
+}> = [
+ {
+ id: `value_labels_hide`,
+ value: 'hide',
+ label: i18n.translate('xpack.lens.xyChart.valueLabelsVisibility.auto', {
+ defaultMessage: 'Hide',
+ }),
+ },
+ {
+ id: `value_labels_inside`,
+ value: 'inside',
+ label: i18n.translate('xpack.lens.xyChart.valueLabelsVisibility.inside', {
+ defaultMessage: 'Show',
+ }),
+ },
+];
+
export function LayerContextMenu(props: VisualizationLayerWidgetProps) {
const { state, layerId } = props;
const horizontalOnly = isHorizontalChart(state.layers);
@@ -118,12 +151,24 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps) {
}
export function XyToolbar(props: VisualizationToolbarProps) {
- const { state, setState } = props;
+ const { state, setState, frame } = props;
const hasNonBarSeries = state?.layers.some(({ seriesType }) =>
['area_stacked', 'area', 'line'].includes(seriesType)
);
+ const hasBarNotStacked = state?.layers.some(({ seriesType }) =>
+ ['bar', 'bar_horizontal'].includes(seriesType)
+ );
+
+ const isAreaPercentage = state?.layers.some(
+ ({ seriesType }) => seriesType === 'area_percentage_stacked'
+ );
+
+ const isHistogramSeries = Boolean(
+ hasHistogramSeries(state?.layers as ValidLayer[], frame.datasourceLayers)
+ );
+
const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false;
const axisGroups = getAxesConfiguration(state?.layers, shouldRotate);
@@ -191,54 +236,99 @@ export function XyToolbar(props: VisualizationToolbarProps) {
: !state?.legend.isVisible
? 'hide'
: 'show';
+
+ const valueLabelsVisibilityMode = state?.valueLabels || 'hide';
+
+ const isValueLabelsEnabled = !hasNonBarSeries && hasBarNotStacked && !isHistogramSeries;
+ const isFittingEnabled = hasNonBarSeries;
+
return (
-
- {
- return {
- value: id,
- dropdownDisplay: (
- <>
- {title}
-
- {description}
-
- >
- ),
- inputDisplay: title,
- };
+ {isValueLabelsEnabled ? (
+
+ {i18n.translate('xpack.lens.shared.chartValueLabelVisibilityLabel', {
+ defaultMessage: 'Labels',
+ })}
+
+ }
+ >
+ value === valueLabelsVisibilityMode)!
+ .id
+ }
+ onChange={(modeId) => {
+ const newMode = valueLabelsOptions.find(({ id }) => id === modeId)!.value;
+ setState({ ...state, valueLabels: newMode });
+ }}
+ />
+
+ ) : null}
+ {isFittingEnabled ? (
+ setState({ ...state, fittingFunction: value })}
- itemLayoutAlign="top"
- hasDividers
- />
-
+ >
+ {
+ return {
+ value: id,
+ dropdownDisplay: (
+ <>
+ {title}
+
+ {description}
+
+ >
+ ),
+ inputDisplay: title,
+ };
+ })}
+ valueOfSelected={state?.fittingFunction || 'None'}
+ onChange={(value) => setState({ ...state, fittingFunction: value })}
+ itemLayoutAlign="top"
+ hasDividers
+ />
+
+ ) : null}
{
keptLayerIds: [],
state: {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -249,6 +250,7 @@ describe('xy_suggestions', () => {
keptLayerIds: ['first'],
state: {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -289,6 +291,7 @@ describe('xy_suggestions', () => {
keptLayerIds: ['first', 'second'],
state: {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -523,6 +526,7 @@ describe('xy_suggestions', () => {
},
state: {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@@ -575,6 +579,7 @@ describe('xy_suggestions', () => {
test('keeps existing seriesType for initial tables', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
fittingFunction: 'None',
preferredSeriesType: 'line',
layers: [
@@ -608,6 +613,7 @@ describe('xy_suggestions', () => {
test('makes a visible seriesType suggestion for unchanged table without split', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
@@ -648,6 +654,7 @@ describe('xy_suggestions', () => {
test('suggests seriesType and stacking when there is a split', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
@@ -693,6 +700,7 @@ describe('xy_suggestions', () => {
(generateId as jest.Mock).mockReturnValueOnce('dummyCol');
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
fittingFunction: 'None',
preferredSeriesType: 'bar',
layers: [
@@ -725,6 +733,7 @@ describe('xy_suggestions', () => {
test('suggests stacking for unchanged table that has a split', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'None',
layers: [
@@ -760,6 +769,7 @@ describe('xy_suggestions', () => {
test('keeps column to dimension mappings on extended tables', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
@@ -802,6 +812,7 @@ describe('xy_suggestions', () => {
test('changes column mappings when suggestion is reorder', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
@@ -845,6 +856,7 @@ describe('xy_suggestions', () => {
(generateId as jest.Mock).mockReturnValueOnce('dummyCol');
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
+ valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts
index edb7c4ed52243..7bbb039577306 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts
@@ -509,6 +509,7 @@ function buildSuggestion({
const state: State = {
legend: currentState ? currentState.legend : { isVisible: true, position: Position.Right },
+ valueLabels: currentState?.valueLabels || 'hide',
fittingFunction: currentState?.fittingFunction || 'None',
xTitle: currentState?.xTitle,
yTitle: currentState?.yTitle,
diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts
index 469a4023434a8..bcfe11851d1ea 100644
--- a/x-pack/plugins/maps/common/constants.ts
+++ b/x-pack/plugins/maps/common/constants.ts
@@ -60,11 +60,6 @@ export enum LAYER_TYPE {
TILED_VECTOR = 'TILED_VECTOR', // similar to a regular vector-layer, but it consumes the data as .mvt tilea iso GeoJson. It supports similar ad-hoc configurations like a regular vector layer (E.g. using IVectorStyle), although there is some loss of functionality e.g. does not support term joining
}
-export enum SORT_ORDER {
- ASC = 'asc',
- DESC = 'desc',
-}
-
export enum SOURCE_TYPES {
EMS_TMS = 'EMS_TMS',
EMS_FILE = 'EMS_FILE',
@@ -237,6 +232,11 @@ export enum SCALING_TYPES {
MVT = 'MVT',
}
+export enum FORMAT_TYPE {
+ GEOJSON = 'geojson',
+ TOPOJSON = 'topojson',
+}
+
export enum MVT_FIELD_TYPE {
STRING = 'String',
NUMBER = 'Number',
diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts
index 16b60492c9b78..fc691f339f34a 100644
--- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts
+++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts
@@ -5,7 +5,9 @@
*/
/* eslint-disable @typescript-eslint/consistent-type-definitions */
-import { RENDER_AS, SORT_ORDER, SCALING_TYPES } from '../constants';
+import { Query } from 'src/plugins/data/public';
+import { SortDirection } from 'src/plugins/data/common/search';
+import { RENDER_AS, SCALING_TYPES } from '../constants';
import { MapExtent, MapQuery } from './map_descriptor';
import { Filter, TimeRange } from '../../../../../src/plugins/data/common';
@@ -22,7 +24,7 @@ export type MapFilters = {
type ESSearchSourceSyncMeta = {
sortField: string;
- sortOrder: SORT_ORDER;
+ sortOrder: SortDirection;
scalingType: SCALING_TYPES;
topHitsSplitField: string;
topHitsSize: number;
@@ -45,7 +47,7 @@ export type VectorSourceRequestMeta = MapFilters & {
export type VectorJoinSourceRequestMeta = MapFilters & {
applyGlobalQuery: boolean;
fieldNames: string[];
- sourceQuery: MapQuery;
+ sourceQuery?: Query;
};
export type VectorStyleRequestMeta = MapFilters & {
diff --git a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts
index 400b6a41ead71..3dc90a12513fd 100644
--- a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts
+++ b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts
@@ -7,14 +7,8 @@
import { FeatureCollection } from 'geojson';
import { Query } from 'src/plugins/data/public';
-import {
- AGG_TYPE,
- GRID_RESOLUTION,
- RENDER_AS,
- SORT_ORDER,
- SCALING_TYPES,
- MVT_FIELD_TYPE,
-} from '../constants';
+import { SortDirection } from 'src/plugins/data/common/search';
+import { AGG_TYPE, GRID_RESOLUTION, RENDER_AS, SCALING_TYPES, MVT_FIELD_TYPE } from '../constants';
export type AttributionDescriptor = {
attributionText?: string;
@@ -40,6 +34,7 @@ export type EMSFileSourceDescriptor = AbstractSourceDescriptor & {
export type AbstractESSourceDescriptor = AbstractSourceDescriptor & {
// id: UUID
+ id: string;
indexPatternId: string;
geoField?: string;
};
@@ -55,18 +50,20 @@ export type AbstractESAggSourceDescriptor = AbstractESSourceDescriptor & {
};
export type ESGeoGridSourceDescriptor = AbstractESAggSourceDescriptor & {
- requestType?: RENDER_AS;
- resolution?: GRID_RESOLUTION;
+ geoField: string;
+ requestType: RENDER_AS;
+ resolution: GRID_RESOLUTION;
};
export type ESSearchSourceDescriptor = AbstractESSourceDescriptor & {
+ geoField: string;
filterByMapBounds?: boolean;
tooltipProperties?: string[];
- sortField?: string;
- sortOrder?: SORT_ORDER;
+ sortField: string;
+ sortOrder: SortDirection;
scalingType: SCALING_TYPES;
- topHitsSplitField?: string;
- topHitsSize?: number;
+ topHitsSplitField: string;
+ topHitsSize: number;
};
export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & {
@@ -76,7 +73,7 @@ export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & {
export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & {
indexPatternTitle?: string;
- term?: string; // term field name
+ term: string; // term field name
whereQuery?: Query;
};
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts
index cff8ba119e1de..a757a78cd210b 100644
--- a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts
+++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { FeatureCollection, GeoJsonProperties } from 'geojson';
+import { FeatureCollection, GeoJsonProperties, Polygon } from 'geojson';
import { MapExtent } from '../descriptor_types';
-import { ES_GEO_FIELD_TYPE } from '../constants';
+import { ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../constants';
export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent;
@@ -23,3 +23,31 @@ export function hitsToGeoJson(
geoFieldType: ES_GEO_FIELD_TYPE,
epochMillisFields: string[]
): FeatureCollection;
+
+export interface ESBBox {
+ top_left: number[];
+ bottom_right: number[];
+}
+
+export interface ESGeoBoundingBoxFilter {
+ geo_bounding_box: {
+ [geoFieldName: string]: ESBBox;
+ };
+}
+
+export interface ESPolygonFilter {
+ geo_shape: {
+ [geoFieldName: string]: {
+ shape: Polygon;
+ relation: ES_SPATIAL_RELATIONS.INTERSECTS;
+ };
+ };
+}
+
+export function createExtentFilter(
+ mapExtent: MapExtent,
+ geoFieldName: string,
+ geoFieldType: ES_GEO_FIELD_TYPE
+): ESPolygonFilter | ESGeoBoundingBoxFilter;
+
+export function makeESBbox({ maxLat, maxLon, minLat, minLon }: MapExtent): ESBBox;
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts
index f157ffe9f1c80..99c1fa3070fb9 100644
--- a/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts
+++ b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts
@@ -8,7 +8,10 @@ import _ from 'lodash';
import { IndexPattern, IFieldType } from '../../../../../src/plugins/data/common';
import { TOP_TERM_PERCENTAGE_SUFFIX } from '../constants';
-export function getField(indexPattern: IndexPattern, fieldName: string) {
+export type BucketProperties = Record;
+export type PropertiesMap = Map;
+
+export function getField(indexPattern: IndexPattern, fieldName: string): IFieldType {
const field = indexPattern.fields.getByName(fieldName);
if (!field) {
throw new Error(
@@ -33,9 +36,10 @@ export function addFieldToDSL(dsl: object, field: IFieldType) {
};
}
-export type BucketProperties = Record;
-
-export function extractPropertiesFromBucket(bucket: any, ignoreKeys: string[] = []) {
+export function extractPropertiesFromBucket(
+ bucket: any,
+ ignoreKeys: string[] = []
+): BucketProperties {
const properties: BucketProperties = {};
for (const key in bucket) {
if (ignoreKeys.includes(key) || !bucket.hasOwnProperty(key)) {
diff --git a/x-pack/plugins/maps/common/migrations/top_hits_time_to_sort.js b/x-pack/plugins/maps/common/migrations/top_hits_time_to_sort.js
index 6b7a5931255c5..9b63db63b967d 100644
--- a/x-pack/plugins/maps/common/migrations/top_hits_time_to_sort.js
+++ b/x-pack/plugins/maps/common/migrations/top_hits_time_to_sort.js
@@ -5,7 +5,8 @@
*/
import _ from 'lodash';
-import { SOURCE_TYPES, SORT_ORDER } from '../constants';
+import { SOURCE_TYPES } from '../constants';
+import { SortDirection } from '../../../../../src/plugins/data/common/search';
function isEsDocumentSource(layerDescriptor) {
const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type');
@@ -23,7 +24,7 @@ export function topHitsTimeToSort({ attributes }) {
if (_.has(layerDescriptor, 'sourceDescriptor.topHitsTimeField')) {
layerDescriptor.sourceDescriptor.sortField =
layerDescriptor.sourceDescriptor.topHitsTimeField;
- layerDescriptor.sourceDescriptor.sortOrder = SORT_ORDER.DESC;
+ layerDescriptor.sourceDescriptor.sortOrder = SortDirection.desc;
delete layerDescriptor.sourceDescriptor.topHitsTimeField;
}
}
diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json
index 6f3a5b61ddc6c..f52feb3552abb 100644
--- a/x-pack/plugins/maps/kibana.json
+++ b/x-pack/plugins/maps/kibana.json
@@ -22,5 +22,5 @@
"ui": true,
"server": true,
"extraPublicDirs": ["common/constants"],
- "requiredBundles": ["kibanaReact", "kibanaUtils", "home"]
+ "requiredBundles": ["kibanaReact", "kibanaUtils", "home", "mapsOss"]
}
diff --git a/x-pack/plugins/maps/public/classes/fields/kibana_region_field.ts b/x-pack/plugins/maps/public/classes/fields/kibana_region_field.ts
index ce72f01adb5f8..622cf68948f99 100644
--- a/x-pack/plugins/maps/public/classes/fields/kibana_region_field.ts
+++ b/x-pack/plugins/maps/public/classes/fields/kibana_region_field.ts
@@ -5,12 +5,12 @@
*/
import { IField, AbstractField } from './field';
-import { IKibanaRegionSource } from '../sources/kibana_regionmap_source/kibana_regionmap_source';
+import { KibanaRegionmapSource } from '../sources/kibana_regionmap_source/kibana_regionmap_source';
import { FIELD_ORIGIN } from '../../../common/constants';
import { IVectorSource } from '../sources/vector_source';
export class KibanaRegionField extends AbstractField implements IField {
- private readonly _source: IKibanaRegionSource;
+ private readonly _source: KibanaRegionmapSource;
constructor({
fieldName,
@@ -18,7 +18,7 @@ export class KibanaRegionField extends AbstractField implements IField {
origin,
}: {
fieldName: string;
- source: IKibanaRegionSource;
+ source: KibanaRegionmapSource;
origin: FIELD_ORIGIN;
}) {
super({ fieldName, origin });
diff --git a/x-pack/plugins/maps/public/classes/joins/inner_join.d.ts b/x-pack/plugins/maps/public/classes/joins/inner_join.d.ts
index 3e2ceac4971c4..987e7bc93c2f6 100644
--- a/x-pack/plugins/maps/public/classes/joins/inner_join.d.ts
+++ b/x-pack/plugins/maps/public/classes/joins/inner_join.d.ts
@@ -5,19 +5,20 @@
*/
import { Feature, GeoJsonProperties } from 'geojson';
-import { IESTermSource } from '../sources/es_term_source';
-import { IJoin, PropertiesMap } from './join';
+import { ESTermSource } from '../sources/es_term_source';
+import { IJoin } from './join';
import { JoinDescriptor } from '../../../common/descriptor_types';
import { ISource } from '../sources/source';
import { ITooltipProperty } from '../tooltips/tooltip_property';
import { IField } from '../fields/field';
+import { PropertiesMap } from '../../../common/elasticsearch_util';
export class InnerJoin implements IJoin {
constructor(joinDescriptor: JoinDescriptor, leftSource: ISource);
destroy: () => void;
- getRightJoinSource(): IESTermSource;
+ getRightJoinSource(): ESTermSource;
toDescriptor(): JoinDescriptor;
diff --git a/x-pack/plugins/maps/public/classes/joins/join.ts b/x-pack/plugins/maps/public/classes/joins/join.ts
index df6f6f684f4d2..465ffbda27303 100644
--- a/x-pack/plugins/maps/public/classes/joins/join.ts
+++ b/x-pack/plugins/maps/public/classes/joins/join.ts
@@ -5,18 +5,16 @@
*/
import { Feature, GeoJsonProperties } from 'geojson';
-import { IESTermSource } from '../sources/es_term_source';
+import { ESTermSource } from '../sources/es_term_source';
import { JoinDescriptor } from '../../../common/descriptor_types';
import { ITooltipProperty } from '../tooltips/tooltip_property';
import { IField } from '../fields/field';
-import { BucketProperties } from '../../../common/elasticsearch_util';
-
-export type PropertiesMap = Map;
+import { PropertiesMap } from '../../../common/elasticsearch_util';
export interface IJoin {
destroy: () => void;
- getRightJoinSource: () => IESTermSource;
+ getRightJoinSource: () => ESTermSource;
toDescriptor: () => JoinDescriptor;
diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts
index 65a76f0c54ffb..2ab8a70f2e4df 100644
--- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts
+++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts
@@ -25,7 +25,6 @@ import { ESGeoGridSource } from '../../sources/es_geo_grid_source/es_geo_grid_so
import { canSkipSourceUpdate } from '../../util/can_skip_fetch';
import { IVectorLayer } from '../vector_layer/vector_layer';
import { IESSource } from '../../sources/es_source';
-import { IESAggSource } from '../../sources/es_agg_source';
import { ISource } from '../../sources/source';
import { DataRequestContext } from '../../../actions';
import { DataRequestAbortError } from '../../util/data_request';
@@ -36,9 +35,11 @@ import {
StylePropertyOptions,
LayerDescriptor,
VectorLayerDescriptor,
+ VectorSourceRequestMeta,
} from '../../../../common/descriptor_types';
import { IVectorSource } from '../../sources/vector_source';
import { LICENSED_FEATURES } from '../../../licensed_features';
+import { ESSearchSource } from '../../sources/es_search_source/es_search_source';
const ACTIVE_COUNT_DATA_ID = 'ACTIVE_COUNT_DATA_ID';
@@ -50,7 +51,7 @@ function getAggType(dynamicProperty: IDynamicStyleProperty {
const source = this.getSource();
return await source.getImmutableProperties();
}
diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts
index e6349fbe9ab9d..66eba3a539801 100644
--- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts
+++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts
@@ -175,6 +175,7 @@ describe('createLayerDescriptor', () => {
query: 'processor.event:"transaction"',
},
sourceDescriptor: {
+ applyGlobalQuery: true,
geoField: 'client.geo.location',
id: '12345',
indexPatternId: 'apm_static_index_pattern_id',
@@ -216,6 +217,7 @@ describe('createLayerDescriptor', () => {
query: 'processor.event:"transaction"',
},
sourceDescriptor: {
+ applyGlobalQuery: true,
geoField: 'client.geo.location',
id: '12345',
indexPatternId: 'apm_static_index_pattern_id',
diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts
index d02f07923c682..22456527491eb 100644
--- a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts
+++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts
@@ -21,7 +21,7 @@ jest.mock('uuid/v4', () => {
import { createSecurityLayerDescriptors } from './create_layer_descriptors';
describe('createLayerDescriptor', () => {
- test('amp index', () => {
+ test('apm index', () => {
expect(createSecurityLayerDescriptors('id', 'apm-*-transaction*')).toEqual([
{
__dataRequests: [],
@@ -32,6 +32,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24,
minZoom: 0,
sourceDescriptor: {
+ applyGlobalQuery: true,
filterByMapBounds: true,
geoField: 'client.geo.location',
id: '12345',
@@ -138,6 +139,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24,
minZoom: 0,
sourceDescriptor: {
+ applyGlobalQuery: true,
filterByMapBounds: true,
geoField: 'server.geo.location',
id: '12345',
@@ -244,6 +246,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24,
minZoom: 0,
sourceDescriptor: {
+ applyGlobalQuery: true,
destGeoField: 'server.geo.location',
id: '12345',
indexPatternId: 'id',
@@ -362,6 +365,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24,
minZoom: 0,
sourceDescriptor: {
+ applyGlobalQuery: true,
filterByMapBounds: true,
geoField: 'source.geo.location',
id: '12345',
@@ -468,6 +472,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24,
minZoom: 0,
sourceDescriptor: {
+ applyGlobalQuery: true,
filterByMapBounds: true,
geoField: 'destination.geo.location',
id: '12345',
@@ -574,6 +579,7 @@ describe('createLayerDescriptor', () => {
maxZoom: 24,
minZoom: 0,
sourceDescriptor: {
+ applyGlobalQuery: true,
destGeoField: 'destination.geo.location',
id: '12345',
indexPatternId: 'id',
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx
index c44ebcf969f7c..b9d7834896245 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx
@@ -46,18 +46,20 @@ import {
DynamicStylePropertyOptions,
MapFilters,
MapQuery,
+ VectorJoinSourceRequestMeta,
VectorLayerDescriptor,
VectorSourceRequestMeta,
VectorStyleRequestMeta,
} from '../../../../common/descriptor_types';
import { IVectorSource } from '../../sources/vector_source';
import { CustomIconAndTooltipContent, ILayer } from '../layer';
-import { IJoin, PropertiesMap } from '../../joins/join';
+import { IJoin } from '../../joins/join';
import { IField } from '../../fields/field';
import { DataRequestContext } from '../../../actions';
import { ITooltipProperty } from '../../tooltips/tooltip_property';
import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
import { IESSource } from '../../sources/es_source';
+import { PropertiesMap } from '../../../../common/elasticsearch_util';
interface SourceResult {
refreshed: boolean;
@@ -239,7 +241,7 @@ export class VectorLayer extends AbstractLayer {
}
const requestToken = Symbol(`${SOURCE_BOUNDS_DATA_REQUEST_ID}-${this.getId()}`);
- const searchFilters = this._getSearchFilters(
+ const searchFilters: VectorSourceRequestMeta = this._getSearchFilters(
dataFilters,
this.getSource(),
this.getCurrentStyle()
@@ -324,7 +326,7 @@ export class VectorLayer extends AbstractLayer {
const joinSource = join.getRightJoinSource();
const sourceDataId = join.getSourceDataRequestId();
const requestToken = Symbol(`layer-join-refresh:${this.getId()} - ${sourceDataId}`);
- const searchFilters = {
+ const searchFilters: VectorJoinSourceRequestMeta = {
...dataFilters,
fieldNames: joinSource.getFieldNames(),
sourceQuery: joinSource.getWhereQuery(),
@@ -386,9 +388,11 @@ export class VectorLayer extends AbstractLayer {
source: IVectorSource,
style: IVectorStyle
): VectorSourceRequestMeta {
+ const styleFieldNames =
+ style.getType() === LAYER_STYLE_TYPE.VECTOR ? style.getSourceFieldNames() : [];
const fieldNames = [
...source.getFieldNames(),
- ...(style.getType() === LAYER_STYLE_TYPE.VECTOR ? style.getSourceFieldNames() : []),
+ ...styleFieldNames,
...this.getValidJoins().map((join) => join.getLeftField().getName()),
];
@@ -464,7 +468,11 @@ export class VectorLayer extends AbstractLayer {
} = syncContext;
const dataRequestId = SOURCE_DATA_REQUEST_ID;
const requestToken = Symbol(`layer-${this.getId()}-${dataRequestId}`);
- const searchFilters = this._getSearchFilters(dataFilters, source, style);
+ const searchFilters: VectorSourceRequestMeta = this._getSearchFilters(
+ dataFilters,
+ source,
+ style
+ );
const prevDataRequest = this.getSourceDataRequest();
const canSkipFetch = await canSkipSourceUpdate({
source,
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js
index 96dad0c01139e..dc3ace69e5a61 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js
+++ b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js
@@ -11,7 +11,7 @@ import { isRetina } from '../../../meta';
import {
addSpriteSheetToMapFromImageData,
loadSpriteSheetImageData,
-} from '../../../connected_components/map/mb/utils'; //todo move this implementation
+} from '../../../connected_components/mb_map/utils'; //todo move this implementation
const MB_STYLE_TYPE_TO_OPACITY = {
fill: ['fill-opacity'],
diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx
index 38e13a68437c7..780b771336b34 100644
--- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx
@@ -11,7 +11,12 @@ import { Adapters } from 'src/plugins/inspector/public';
import { FileLayer } from '@elastic/ems-client';
import { Attribution, ImmutableSourceProperty, SourceEditorArgs } from '../source';
import { AbstractVectorSource, GeoJsonWithMeta, IVectorSource } from '../vector_source';
-import { SOURCE_TYPES, FIELD_ORIGIN, VECTOR_SHAPE_TYPE } from '../../../../common/constants';
+import {
+ SOURCE_TYPES,
+ FIELD_ORIGIN,
+ VECTOR_SHAPE_TYPE,
+ FORMAT_TYPE,
+} from '../../../../common/constants';
import { getEmsFileLayers } from '../../../meta';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { UpdateSourceEditor } from './update_source_editor';
@@ -30,11 +35,9 @@ export const sourceTitle = i18n.translate('xpack.maps.source.emsFileTitle', {
});
export class EMSFileSource extends AbstractVectorSource implements IEmsFileSource {
- static type = SOURCE_TYPES.EMS_FILE;
-
static createDescriptor({ id, tooltipProperties = [] }: Partial) {
return {
- type: EMSFileSource.type,
+ type: SOURCE_TYPES.EMS_FILE,
id: id!,
tooltipProperties,
};
@@ -99,7 +102,7 @@ export class EMSFileSource extends AbstractVectorSource implements IEmsFileSourc
const emsFileLayer = await this.getEMSFileLayer();
// @ts-ignore
const featureCollection = await AbstractVectorSource.getGeoJson({
- format: emsFileLayer.getDefaultFormatType(),
+ format: emsFileLayer.getDefaultFormatType() as FORMAT_TYPE,
featureCollectionPath: 'data',
fetchUrl: emsFileLayer.getDefaultFormatUrl(),
});
diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js
index b364dd32860f3..b294f201def4c 100644
--- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import _ from 'lodash';
import React from 'react';
import { AbstractTMSSource } from '../tms_source';
import { getEmsTmsServices } from '../../../meta';
@@ -20,25 +19,18 @@ export const sourceTitle = i18n.translate('xpack.maps.source.emsTileTitle', {
});
export class EMSTMSSource extends AbstractTMSSource {
- static type = SOURCE_TYPES.EMS_TMS;
-
- static createDescriptor(sourceConfig) {
+ static createDescriptor(descriptor) {
return {
- type: EMSTMSSource.type,
- id: sourceConfig.id,
- isAutoSelect: sourceConfig.isAutoSelect,
+ type: SOURCE_TYPES.EMS_TMS,
+ id: descriptor.id,
+ isAutoSelect:
+ typeof descriptor.isAutoSelect !== 'undefined' ? !!descriptor.isAutoSelect : false,
};
}
constructor(descriptor, inspectorAdapters) {
- super(
- {
- id: descriptor.id,
- type: EMSTMSSource.type,
- isAutoSelect: _.get(descriptor, 'isAutoSelect', false),
- },
- inspectorAdapters
- );
+ descriptor = EMSTMSSource.createDescriptor(descriptor);
+ super(descriptor, inspectorAdapters);
}
renderSourceSettingsEditor({ onChange }) {
diff --git a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts
index 5c062f3419e28..dc95632032fa9 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts
+++ b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts
@@ -33,9 +33,21 @@ export abstract class AbstractESAggSource extends AbstractESSource {
private readonly _metricFields: IESAggField[];
private readonly _canReadFromGeoJson: boolean;
+ static createDescriptor(
+ descriptor: Partial
+ ): AbstractESAggSourceDescriptor {
+ const normalizedDescriptor = AbstractESSource.createDescriptor(descriptor);
+ return {
+ ...normalizedDescriptor,
+ type: descriptor.type ? descriptor.type : '',
+ metrics:
+ descriptor.metrics && descriptor.metrics.length > 0 ? descriptor.metrics : [DEFAULT_METRIC],
+ };
+ }
+
constructor(
descriptor: AbstractESAggSourceDescriptor,
- inspectorAdapters: Adapters,
+ inspectorAdapters?: Adapters,
canReadFromGeoJson = true
) {
super(descriptor, inspectorAdapters);
@@ -55,7 +67,7 @@ export abstract class AbstractESAggSource extends AbstractESSource {
}
}
- getFieldByName(fieldName: string) {
+ getFieldByName(fieldName: string): IField | null {
return this.getMetricFieldForName(fieldName);
}
@@ -113,7 +125,7 @@ export abstract class AbstractESAggSource extends AbstractESSource {
}
}
- async getFields() {
+ async getFields(): Promise {
return this.getMetricFields();
}
@@ -128,7 +140,7 @@ export abstract class AbstractESAggSource extends AbstractESSource {
return valueAggsDsl;
}
- async getTooltipProperties(properties: GeoJsonProperties) {
+ async getTooltipProperties(properties: GeoJsonProperties): Promise {
const metricFields = await this.getFields();
const promises: Array> = [];
metricFields.forEach((metricField) => {
diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts
deleted file mode 100644
index b221d13bb0f8a..0000000000000
--- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts
+++ /dev/null
@@ -1,46 +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 { AbstractESAggSource } from '../es_agg_source';
-import {
- ESGeoGridSourceDescriptor,
- MapFilters,
- MapQuery,
- VectorSourceSyncMeta,
- VectorSourceRequestMeta,
-} from '../../../../common/descriptor_types';
-import { GRID_RESOLUTION } from '../../../../common/constants';
-import { IField } from '../../fields/field';
-import { ITiledSingleLayerVectorSource } from '../vector_source';
-
-export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingleLayerVectorSource {
- static createDescriptor({
- indexPatternId,
- geoField,
- requestType,
- resolution,
- }: Partial): ESGeoGridSourceDescriptor;
-
- constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown);
-
- readonly _descriptor: ESGeoGridSourceDescriptor;
-
- getFieldNames(): string[];
- getGridResolution(): GRID_RESOLUTION;
- getGeoGridPrecision(zoom: number): number;
- createField({ fieldName }: { fieldName: string }): IField;
-
- getLayerName(): string;
-
- getUrlTemplateWithMeta(
- searchFilters: VectorSourceRequestMeta
- ): Promise<{
- layerName: string;
- urlTemplate: string;
- minSourceZoom: number;
- maxSourceZoom: number;
- }>;
-}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx
similarity index 63%
rename from x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js
rename to x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx
index 181af6b17b7dd..6ec51b8e118cb 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx
@@ -4,37 +4,51 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
-import uuid from 'uuid/v4';
+import React, { ReactElement } from 'react';
+import { i18n } from '@kbn/i18n';
+import rison from 'rison-node';
+import { Feature } from 'geojson';
+import { SearchResponse } from 'elasticsearch';
import {
convertCompositeRespToGeoJson,
convertRegularRespToGeoJson,
makeESBbox,
} from '../../../../common/elasticsearch_util';
+// @ts-expect-error
import { UpdateSourceEditor } from './update_source_editor';
import {
- SOURCE_TYPES,
DEFAULT_MAX_BUCKETS_LIMIT,
- RENDER_AS,
- GRID_RESOLUTION,
- VECTOR_SHAPE_TYPE,
- MVT_SOURCE_LAYER_NAME,
+ ES_GEO_FIELD_TYPE,
+ GEOCENTROID_AGG_NAME,
+ GEOTILE_GRID_AGG_NAME,
GIS_API_PATH,
+ GRID_RESOLUTION,
MVT_GETGRIDTILE_API_PATH,
- GEOTILE_GRID_AGG_NAME,
- GEOCENTROID_AGG_NAME,
- ES_GEO_FIELD_TYPE,
+ MVT_SOURCE_LAYER_NAME,
+ RENDER_AS,
+ SOURCE_TYPES,
+ VECTOR_SHAPE_TYPE,
} from '../../../../common/constants';
-import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
-import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source';
+import { AbstractESAggSource } from '../es_agg_source';
import { DataRequestAbortError } from '../../util/data_request';
import { registerSource } from '../source_registry';
import { LICENSED_FEATURES } from '../../../licensed_features';
-import rison from 'rison-node';
import { getHttp } from '../../../kibana_services';
+import { GeoJsonWithMeta, ITiledSingleLayerVectorSource } from '../vector_source';
+import {
+ ESGeoGridSourceDescriptor,
+ MapExtent,
+ VectorSourceRequestMeta,
+ VectorSourceSyncMeta,
+} from '../../../../common/descriptor_types';
+import { ImmutableSourceProperty, SourceEditorArgs } from '../source';
+import { ISearchSource } from '../../../../../../../src/plugins/data/common/search/search_source';
+import { IndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns';
+import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
+import { isValidStringConfig } from '../../util/valid_string_config';
export const MAX_GEOTILE_LEVEL = 29;
@@ -46,31 +60,41 @@ export const heatmapTitle = i18n.translate('xpack.maps.source.esGridHeatmapTitle
defaultMessage: 'Heat map',
});
-export class ESGeoGridSource extends AbstractESAggSource {
- static type = SOURCE_TYPES.ES_GEO_GRID;
-
- static createDescriptor({ indexPatternId, geoField, metrics, requestType, resolution }) {
+export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingleLayerVectorSource {
+ static createDescriptor(
+ descriptor: Partial
+ ): ESGeoGridSourceDescriptor {
+ const normalizedDescriptor = AbstractESAggSource.createDescriptor(descriptor);
+ if (!isValidStringConfig(normalizedDescriptor.geoField)) {
+ throw new Error('Cannot create an ESGeoGridSourceDescriptor without a geoField');
+ }
return {
- type: ESGeoGridSource.type,
- id: uuid(),
- indexPatternId,
- geoField,
- metrics: metrics ? metrics : [DEFAULT_METRIC],
- requestType,
- resolution: resolution ? resolution : GRID_RESOLUTION.COARSE,
+ ...normalizedDescriptor,
+ type: SOURCE_TYPES.ES_GEO_GRID,
+ geoField: normalizedDescriptor.geoField!,
+ requestType: descriptor.requestType || RENDER_AS.POINT,
+ resolution: descriptor.resolution ? descriptor.resolution : GRID_RESOLUTION.COARSE,
};
}
- constructor(descriptor, inspectorAdapters) {
- super(descriptor, inspectorAdapters, descriptor.resolution !== GRID_RESOLUTION.SUPER_FINE);
+ readonly _descriptor: ESGeoGridSourceDescriptor;
+
+ constructor(descriptor: Partial, inspectorAdapters?: Adapters) {
+ const sourceDescriptor = ESGeoGridSource.createDescriptor(descriptor);
+ super(
+ sourceDescriptor,
+ inspectorAdapters,
+ descriptor.resolution !== GRID_RESOLUTION.SUPER_FINE
+ );
+ this._descriptor = sourceDescriptor;
}
- renderSourceSettingsEditor({ onChange, currentLayerType }) {
+ renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement {
return (
{
+ let indexPatternName = this.getIndexPatternId();
try {
const indexPattern = await this.getIndexPattern();
- indexPatternTitle = indexPattern.title;
+ indexPatternName = indexPattern.title;
} catch (error) {
// ignore error, title will just default to id
}
@@ -102,7 +126,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
label: i18n.translate('xpack.maps.source.esGrid.indexPatternLabel', {
defaultMessage: 'Index pattern',
}),
- value: indexPatternTitle,
+ value: indexPatternName,
},
{
label: i18n.translate('xpack.maps.source.esGrid.geospatialFieldLabel', {
@@ -117,7 +141,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName());
}
- isGeoGridPrecisionAware() {
+ isGeoGridPrecisionAware(): boolean {
if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) {
// MVT gridded data should not bootstrap each time the precision changes
// mapbox-gl needs to handle this
@@ -128,15 +152,15 @@ export class ESGeoGridSource extends AbstractESAggSource {
}
}
- showJoinEditor() {
+ showJoinEditor(): boolean {
return false;
}
- getGridResolution() {
+ getGridResolution(): GRID_RESOLUTION {
return this._descriptor.resolution;
}
- getGeoGridPrecision(zoom) {
+ getGeoGridPrecision(zoom: number): number {
if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) {
// The target-precision needs to be determined server side.
return NaN;
@@ -178,9 +202,18 @@ export class ESGeoGridSource extends AbstractESAggSource {
bucketsPerGrid,
isRequestStillActive,
bufferedExtent,
+ }: {
+ searchSource: ISearchSource;
+ indexPattern: IndexPattern;
+ precision: number;
+ layerName: string;
+ registerCancelCallback: (callback: () => void) => void;
+ bucketsPerGrid: number;
+ isRequestStillActive: () => boolean;
+ bufferedExtent: MapExtent;
}) {
- const gridsPerRequest = Math.floor(DEFAULT_MAX_BUCKETS_LIMIT / bucketsPerGrid);
- const aggs = {
+ const gridsPerRequest: number = Math.floor(DEFAULT_MAX_BUCKETS_LIMIT / bucketsPerGrid);
+ const aggs: any = {
compositeSplit: {
composite: {
size: gridsPerRequest,
@@ -232,8 +265,10 @@ export class ESGeoGridSource extends AbstractESAggSource {
aggs.compositeSplit.composite.after = afterKey;
}
searchSource.setField('aggs', aggs);
- const requestId = afterKey ? `${this.getId()} afterKey ${afterKey.geoSplit}` : this.getId();
- const esResponse = await this._runEsQuery({
+ const requestId: string = afterKey
+ ? `${this.getId()} afterKey ${afterKey.geoSplit}`
+ : this.getId();
+ const esResponse: SearchResponse = await this._runEsQuery({
requestId,
requestName: `${layerName} (${requestCount})`,
searchSource,
@@ -259,7 +294,12 @@ export class ESGeoGridSource extends AbstractESAggSource {
return features;
}
- _addNonCompositeAggsToSearchSource(searchSource, indexPattern, precision, bufferedExtent) {
+ _addNonCompositeAggsToSearchSource(
+ searchSource: ISearchSource,
+ indexPattern: IndexPattern,
+ precision: number | null,
+ bufferedExtent?: MapExtent | null
+ ) {
searchSource.setField('aggs', {
[GEOTILE_GRID_AGG_NAME]: {
geotile_grid: {
@@ -290,7 +330,14 @@ export class ESGeoGridSource extends AbstractESAggSource {
layerName,
registerCancelCallback,
bufferedExtent,
- }) {
+ }: {
+ searchSource: ISearchSource;
+ indexPattern: IndexPattern;
+ precision: number;
+ layerName: string;
+ registerCancelCallback: (callback: () => void) => void;
+ bufferedExtent?: MapExtent;
+ }): Promise {
this._addNonCompositeAggsToSearchSource(searchSource, indexPattern, precision, bufferedExtent);
const esResponse = await this._runEsQuery({
@@ -306,52 +353,69 @@ export class ESGeoGridSource extends AbstractESAggSource {
return convertRegularRespToGeoJson(esResponse, this._descriptor.requestType);
}
- async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback, isRequestStillActive) {
- const indexPattern = await this.getIndexPattern();
- const searchSource = await this.makeSearchSource(searchFilters, 0);
+ async getGeoJsonWithMeta(
+ layerName: string,
+ searchFilters: VectorSourceRequestMeta,
+ registerCancelCallback: (callback: () => void) => void,
+ isRequestStillActive: () => boolean
+ ): Promise {
+ const indexPattern: IndexPattern = await this.getIndexPattern();
+ const searchSource: ISearchSource = await this.makeSearchSource(searchFilters, 0);
let bucketsPerGrid = 1;
this.getMetricFields().forEach((metricField) => {
bucketsPerGrid += metricField.getBucketCount();
});
- const features =
- bucketsPerGrid === 1
- ? await this._nonCompositeAggRequest({
- searchSource,
- indexPattern,
- precision: searchFilters.geogridPrecision,
- layerName,
- registerCancelCallback,
- bufferedExtent: searchFilters.buffer,
- })
- : await this._compositeAggRequest({
- searchSource,
- indexPattern,
- precision: searchFilters.geogridPrecision,
- layerName,
- registerCancelCallback,
- bucketsPerGrid,
- isRequestStillActive,
- bufferedExtent: searchFilters.buffer,
- });
+ let features: Feature[];
+ if (searchFilters.buffer) {
+ features =
+ bucketsPerGrid === 1
+ ? await this._nonCompositeAggRequest({
+ searchSource,
+ indexPattern,
+ precision: searchFilters.geogridPrecision || 0,
+ layerName,
+ registerCancelCallback,
+ bufferedExtent: searchFilters.buffer,
+ })
+ : await this._compositeAggRequest({
+ searchSource,
+ indexPattern,
+ precision: searchFilters.geogridPrecision || 0,
+ layerName,
+ registerCancelCallback,
+ bucketsPerGrid,
+ isRequestStillActive,
+ bufferedExtent: searchFilters.buffer,
+ });
+ } else {
+ throw new Error('Cannot get GeoJson without searchFilter.buffer');
+ }
return {
data: {
type: 'FeatureCollection',
- features: features,
+ features,
},
meta: {
areResultsTrimmed: false,
},
- };
+ } as GeoJsonWithMeta;
}
- getLayerName() {
+ getLayerName(): string {
return MVT_SOURCE_LAYER_NAME;
}
- async getUrlTemplateWithMeta(searchFilters) {
+ async getUrlTemplateWithMeta(
+ searchFilters: VectorSourceRequestMeta
+ ): Promise<{
+ layerName: string;
+ urlTemplate: string;
+ minSourceZoom: number;
+ maxSourceZoom: number;
+ }> {
const indexPattern = await this.getIndexPattern();
const searchSource = await this.makeSearchSource(searchFilters, 0);
@@ -376,25 +440,25 @@ export class ESGeoGridSource extends AbstractESAggSource {
layerName: this.getLayerName(),
minSourceZoom: this.getMinZoom(),
maxSourceZoom: this.getMaxZoom(),
- urlTemplate: urlTemplate,
+ urlTemplate,
};
}
- isFilterByMapBounds() {
+ isFilterByMapBounds(): boolean {
if (this._descriptor.resolution === GRID_RESOLUTION.SUPER_FINE) {
- //MVT gridded data. Should exclude bounds-filter from ES-DSL
+ // MVT gridded data. Should exclude bounds-filter from ES-DSL
return false;
} else {
- //Should include bounds-filter from ES-DSL
+ // Should include bounds-filter from ES-DSL
return true;
}
}
- canFormatFeatureProperties() {
+ canFormatFeatureProperties(): boolean {
return true;
}
- async getSupportedShapeTypes() {
+ async getSupportedShapeTypes(): Promise {
if (this._descriptor.requestType === RENDER_AS.GRID) {
return [VECTOR_SHAPE_TYPE.POLYGON];
}
@@ -402,7 +466,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
return [VECTOR_SHAPE_TYPE.POINT];
}
- async getLicensedFeatures() {
+ async getLicensedFeatures(): Promise {
const geoField = await this._getGeoField();
return geoField.type === ES_GEO_FIELD_TYPE.GEO_SHAPE
? [LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE]
diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js
index 0360208ef8370..504212ea1ea84 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js
@@ -5,7 +5,6 @@
*/
import React from 'react';
-import uuid from 'uuid/v4';
import turfBbox from '@turf/bbox';
import { multiPoint } from '@turf/helpers';
@@ -14,7 +13,7 @@ import { i18n } from '@kbn/i18n';
import { SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { convertToLines } from './convert_to_lines';
-import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source';
+import { AbstractESAggSource } from '../es_agg_source';
import { registerSource } from '../source_registry';
import { turfBboxToBounds } from '../../../../common/elasticsearch_util';
import { DataRequestAbortError } from '../../util/data_request';
@@ -28,14 +27,14 @@ export const sourceTitle = i18n.translate('xpack.maps.source.pewPewTitle', {
export class ESPewPewSource extends AbstractESAggSource {
static type = SOURCE_TYPES.ES_PEW_PEW;
- static createDescriptor({ indexPatternId, sourceGeoField, destGeoField, metrics }) {
+ static createDescriptor(descriptor) {
+ const normalizedDescriptor = AbstractESAggSource.createDescriptor(descriptor);
return {
+ ...normalizedDescriptor,
type: ESPewPewSource.type,
- id: uuid(),
- indexPatternId: indexPatternId,
- sourceGeoField,
- destGeoField,
- metrics: metrics ? metrics : [DEFAULT_METRIC],
+ indexPatternId: descriptor.indexPatternId,
+ sourceGeoField: descriptor.sourceGeoField,
+ destGeoField: descriptor.destGeoField,
};
}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/constants.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/constants.ts
new file mode 100644
index 0000000000000..8ddf536cabecd
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/constants.ts
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export const DEFAULT_FILTER_BY_MAP_BOUNDS: boolean = true;
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx
index af2061d6c541f..80cc88f432cad 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx
@@ -16,8 +16,15 @@ import { VectorLayer } from '../../layers/vector_layer/vector_layer';
import { LAYER_WIZARD_CATEGORY, SCALING_TYPES } from '../../../../common/constants';
import { TiledVectorLayer } from '../../layers/tiled_vector_layer/tiled_vector_layer';
import { EsDocumentsLayerIcon } from './es_documents_layer_icon';
+import {
+ ESSearchSourceDescriptor,
+ VectorLayerDescriptor,
+} from '../../../../common/descriptor_types';
-export function createDefaultLayerDescriptor(sourceConfig: unknown, mapColors: string[]) {
+export function createDefaultLayerDescriptor(
+ sourceConfig: Partial,
+ mapColors: string[]
+): VectorLayerDescriptor {
const sourceDescriptor = ESSearchSource.createDescriptor(sourceConfig);
if (sourceDescriptor.scalingType === SCALING_TYPES.CLUSTERS) {
@@ -36,7 +43,7 @@ export const esDocumentsLayerWizardConfig: LayerWizard = {
}),
icon: EsDocumentsLayerIcon,
renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => {
- const onSourceConfigChange = (sourceConfig: unknown) => {
+ const onSourceConfigChange = (sourceConfig: Partial) => {
if (!sourceConfig) {
previewLayers([]);
return;
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.d.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.d.ts
deleted file mode 100644
index 67d68dc065b00..0000000000000
--- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.d.ts
+++ /dev/null
@@ -1,26 +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 { AbstractESSource } from '../es_source';
-import { ESSearchSourceDescriptor, MapFilters } from '../../../../common/descriptor_types';
-import { ITiledSingleLayerVectorSource } from '../vector_source';
-
-export class ESSearchSource extends AbstractESSource implements ITiledSingleLayerVectorSource {
- static createDescriptor(sourceConfig: unknown): ESSearchSourceDescriptor;
-
- constructor(sourceDescriptor: Partial, inspectorAdapters: unknown);
- getFieldNames(): string[];
-
- getUrlTemplateWithMeta(
- searchFilters: MapFilters
- ): Promise<{
- layerName: string;
- urlTemplate: string;
- minSourceZoom: number;
- maxSourceZoom: number;
- }>;
- getLayerName(): string;
-}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts
index 9c1cda4088dcd..e7099115ffe5e 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts
+++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts
@@ -11,21 +11,22 @@ jest.mock('./load_index_settings');
import { getIndexPatternService, getSearchService, getHttp } from '../../../kibana_services';
import { SearchSource } from 'src/plugins/data/public';
-// @ts-expect-error
import { loadIndexSettings } from './load_index_settings';
import { ESSearchSource } from './es_search_source';
import { VectorSourceRequestMeta } from '../../../../common/descriptor_types';
+const mockDescriptor = { indexPatternId: 'foo', geoField: 'bar' };
+
describe('ESSearchSource', () => {
it('constructor', () => {
- const esSearchSource = new ESSearchSource({}, null);
+ const esSearchSource = new ESSearchSource(mockDescriptor);
expect(esSearchSource instanceof ESSearchSource).toBe(true);
});
describe('ITiledSingleLayerVectorSource', () => {
it('mb-source params', () => {
- const esSearchSource = new ESSearchSource({}, null);
+ const esSearchSource = new ESSearchSource(mockDescriptor);
expect(esSearchSource.getMinZoom()).toBe(0);
expect(esSearchSource.getMaxZoom()).toBe(24);
expect(esSearchSource.getLayerName()).toBe('source_layer');
@@ -72,6 +73,7 @@ describe('ESSearchSource', () => {
getIndexPatternService.mockReturnValue(mockIndexPatternService);
// @ts-expect-error
getSearchService.mockReturnValue(mockSearchService);
+ // @ts-expect-error
loadIndexSettings.mockReturnValue({
maxResultWindow: 1000,
});
@@ -104,10 +106,10 @@ describe('ESSearchSource', () => {
};
it('Should only include required props', async () => {
- const esSearchSource = new ESSearchSource(
- { geoField: geoFieldName, indexPatternId: 'ipId' },
- null
- );
+ const esSearchSource = new ESSearchSource({
+ geoField: geoFieldName,
+ indexPatternId: 'ipId',
+ });
const urlTemplateWithMeta = await esSearchSource.getUrlTemplateWithMeta(searchFilters);
expect(urlTemplateWithMeta.urlTemplate).toBe(
`rootdir/api/maps/mvt/getTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fields,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape`
@@ -118,22 +120,28 @@ describe('ESSearchSource', () => {
describe('isFilterByMapBounds', () => {
it('default', () => {
- const esSearchSource = new ESSearchSource({}, null);
+ const esSearchSource = new ESSearchSource(mockDescriptor);
expect(esSearchSource.isFilterByMapBounds()).toBe(true);
});
it('mvt', () => {
- const esSearchSource = new ESSearchSource({ scalingType: SCALING_TYPES.MVT }, null);
+ const esSearchSource = new ESSearchSource({
+ ...mockDescriptor,
+ scalingType: SCALING_TYPES.MVT,
+ });
expect(esSearchSource.isFilterByMapBounds()).toBe(false);
});
});
describe('getJoinsDisabledReason', () => {
it('default', () => {
- const esSearchSource = new ESSearchSource({}, null);
+ const esSearchSource = new ESSearchSource(mockDescriptor);
expect(esSearchSource.getJoinsDisabledReason()).toBe(null);
});
it('mvt', () => {
- const esSearchSource = new ESSearchSource({ scalingType: SCALING_TYPES.MVT }, null);
+ const esSearchSource = new ESSearchSource({
+ ...mockDescriptor,
+ scalingType: SCALING_TYPES.MVT,
+ });
expect(esSearchSource.getJoinsDisabledReason()).toBe(
'Joins are not supported when scaling by mvt vector tiles'
);
@@ -142,12 +150,15 @@ describe('ESSearchSource', () => {
describe('getFields', () => {
it('default', () => {
- const esSearchSource = new ESSearchSource({}, null);
+ const esSearchSource = new ESSearchSource(mockDescriptor);
const docField = esSearchSource.createField({ fieldName: 'prop1' });
expect(docField.canReadFromGeoJson()).toBe(true);
});
it('mvt', () => {
- const esSearchSource = new ESSearchSource({ scalingType: SCALING_TYPES.MVT }, null);
+ const esSearchSource = new ESSearchSource({
+ ...mockDescriptor,
+ scalingType: SCALING_TYPES.MVT,
+ });
const docField = esSearchSource.createField({ fieldName: 'prop1' });
expect(docField.canReadFromGeoJson()).toBe(false);
});
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx
similarity index 68%
rename from x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js
rename to x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx
index da75dfd83d809..d31f6ee626245 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx
@@ -5,50 +5,82 @@
*/
import _ from 'lodash';
-import React from 'react';
+import React, { ReactElement } from 'react';
import rison from 'rison-node';
+import { i18n } from '@kbn/i18n';
+import { IFieldType, IndexPattern } from 'src/plugins/data/public';
+import { FeatureCollection, GeoJsonProperties } from 'geojson';
import { AbstractESSource } from '../es_source';
-import { getSearchService, getHttp } from '../../../kibana_services';
-import { hitsToGeoJson, getField, addFieldToDSL } from '../../../../common/elasticsearch_util';
+import { getHttp, getSearchService } from '../../../kibana_services';
+import { addFieldToDSL, getField, hitsToGeoJson } from '../../../../common/elasticsearch_util';
+// @ts-expect-error
import { UpdateSourceEditor } from './update_source_editor';
+
import {
- SOURCE_TYPES,
- ES_GEO_FIELD_TYPE,
DEFAULT_MAX_BUCKETS_LIMIT,
- SORT_ORDER,
- SCALING_TYPES,
- VECTOR_SHAPE_TYPE,
- MVT_SOURCE_LAYER_NAME,
+ ES_GEO_FIELD_TYPE,
+ FIELD_ORIGIN,
GIS_API_PATH,
MVT_GETTILE_API_PATH,
+ MVT_SOURCE_LAYER_NAME,
+ SCALING_TYPES,
+ SOURCE_TYPES,
+ VECTOR_SHAPE_TYPE,
} from '../../../../common/constants';
-import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { getSourceFields } from '../../../index_pattern_util';
import { loadIndexSettings } from './load_index_settings';
-import uuid from 'uuid/v4';
import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';
import { ESDocField } from '../../fields/es_doc_field';
import { registerSource } from '../source_registry';
+import {
+ ESSearchSourceDescriptor,
+ VectorSourceRequestMeta,
+ VectorSourceSyncMeta,
+} from '../../../../common/descriptor_types';
+import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
+import { ImmutableSourceProperty, PreIndexedShape, SourceEditorArgs } from '../source';
+import { IField } from '../../fields/field';
+import {
+ GeoJsonWithMeta,
+ ITiledSingleLayerVectorSource,
+ SourceTooltipConfig,
+} from '../vector_source';
+import { ITooltipProperty } from '../../tooltips/tooltip_property';
+import { DataRequest } from '../../util/data_request';
+import { SortDirection, SortDirectionNumeric } from '../../../../../../../src/plugins/data/common';
+import { isValidStringConfig } from '../../util/valid_string_config';
export const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', {
defaultMessage: 'Documents',
});
-function getDocValueAndSourceFields(indexPattern, fieldNames) {
- const docValueFields = [];
- const sourceOnlyFields = [];
- const scriptFields = {};
+export interface ScriptField {
+ source: string;
+ lang: string;
+}
+
+function getDocValueAndSourceFields(
+ indexPattern: IndexPattern,
+ fieldNames: string[]
+): {
+ docValueFields: Array;
+ sourceOnlyFields: string[];
+ scriptFields: Record;
+} {
+ const docValueFields: Array = [];
+ const sourceOnlyFields: string[] = [];
+ const scriptFields: Record = {};
fieldNames.forEach((fieldName) => {
const field = getField(indexPattern, fieldName);
if (field.scripted) {
scriptFields[field.name] = {
script: {
- source: field.script,
- lang: field.lang,
+ source: field.script || '',
+ lang: field.lang || '',
},
};
} else if (field.readFromDocValues) {
@@ -68,43 +100,64 @@ function getDocValueAndSourceFields(indexPattern, fieldNames) {
return { docValueFields, sourceOnlyFields, scriptFields };
}
-export class ESSearchSource extends AbstractESSource {
- static type = SOURCE_TYPES.ES_SEARCH;
+export class ESSearchSource extends AbstractESSource implements ITiledSingleLayerVectorSource {
+ readonly _descriptor: ESSearchSourceDescriptor;
+ protected readonly _tooltipFields: ESDocField[];
- static createDescriptor(descriptor) {
+ static createDescriptor(descriptor: Partial): ESSearchSourceDescriptor {
+ const normalizedDescriptor = AbstractESSource.createDescriptor(descriptor);
+ if (!isValidStringConfig(normalizedDescriptor.geoField)) {
+ throw new Error('Cannot create an ESSearchSourceDescriptor without a geoField');
+ }
return {
- ...descriptor,
- id: descriptor.id ? descriptor.id : uuid(),
- type: ESSearchSource.type,
- indexPatternId: descriptor.indexPatternId,
- geoField: descriptor.geoField,
- filterByMapBounds: _.get(descriptor, 'filterByMapBounds', DEFAULT_FILTER_BY_MAP_BOUNDS),
- tooltipProperties: _.get(descriptor, 'tooltipProperties', []),
- sortField: _.get(descriptor, 'sortField', ''),
- sortOrder: _.get(descriptor, 'sortOrder', SORT_ORDER.DESC),
- scalingType: _.get(descriptor, 'scalingType', SCALING_TYPES.LIMIT),
- topHitsSplitField: descriptor.topHitsSplitField,
- topHitsSize: _.get(descriptor, 'topHitsSize', 1),
+ ...normalizedDescriptor,
+ type: SOURCE_TYPES.ES_SEARCH,
+ geoField: normalizedDescriptor.geoField!,
+ filterByMapBounds:
+ typeof descriptor.filterByMapBounds === 'boolean'
+ ? descriptor.filterByMapBounds
+ : DEFAULT_FILTER_BY_MAP_BOUNDS,
+ tooltipProperties: Array.isArray(descriptor.tooltipProperties)
+ ? descriptor.tooltipProperties
+ : [],
+ sortField: isValidStringConfig(descriptor.sortField) ? (descriptor.sortField as string) : '',
+ sortOrder: isValidStringConfig(descriptor.sortOrder)
+ ? descriptor.sortOrder!
+ : SortDirection.desc,
+ scalingType: isValidStringConfig(descriptor.scalingType)
+ ? descriptor.scalingType!
+ : SCALING_TYPES.LIMIT,
+ topHitsSplitField: isValidStringConfig(descriptor.topHitsSplitField)
+ ? descriptor.topHitsSplitField!
+ : '',
+ topHitsSize:
+ typeof descriptor.topHitsSize === 'number' && descriptor.topHitsSize > 0
+ ? descriptor.topHitsSize
+ : 1,
};
}
- constructor(descriptor, inspectorAdapters) {
- super(ESSearchSource.createDescriptor(descriptor), inspectorAdapters);
-
- this._tooltipFields = this._descriptor.tooltipProperties.map((property) =>
- this.createField({ fieldName: property })
- );
+ constructor(descriptor: Partial, inspectorAdapters?: Adapters) {
+ const sourceDescriptor = ESSearchSource.createDescriptor(descriptor);
+ super(sourceDescriptor, inspectorAdapters);
+ this._descriptor = sourceDescriptor;
+ this._tooltipFields = this._descriptor.tooltipProperties
+ ? this._descriptor.tooltipProperties.map((property) => {
+ return this.createField({ fieldName: property });
+ })
+ : [];
}
- createField({ fieldName }) {
+ createField({ fieldName }: { fieldName: string }): ESDocField {
return new ESDocField({
fieldName,
source: this,
+ origin: FIELD_ORIGIN.SOURCE,
canReadFromGeoJson: this._descriptor.scalingType !== SCALING_TYPES.MVT,
});
}
- renderSourceSettingsEditor({ onChange }) {
+ renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null {
const getGeoField = () => {
return this._getGeoField();
};
@@ -113,7 +166,7 @@ export class ESSearchSource extends AbstractESSource {
source={this}
indexPatternId={this.getIndexPatternId()}
getGeoField={getGeoField}
- onChange={onChange}
+ onChange={sourceEditorArgs.onChange}
tooltipFields={this._tooltipFields}
sortField={this._descriptor.sortField}
sortOrder={this._descriptor.sortOrder}
@@ -125,34 +178,36 @@ export class ESSearchSource extends AbstractESSource {
);
}
- async getFields() {
+ async getFields(): Promise {
try {
const indexPattern = await this.getIndexPattern();
- return indexPattern.fields
- .filter((field) => {
- // Ensure fielddata is enabled for field.
- // Search does not request _source
- return field.aggregatable;
- })
- .map((field) => {
+ const fields: IFieldType[] = indexPattern.fields.filter((field) => {
+ // Ensure fielddata is enabled for field.
+ // Search does not request _source
+ return field.aggregatable;
+ });
+
+ return fields.map(
+ (field): IField => {
return this.createField({ fieldName: field.name });
- });
+ }
+ );
} catch (error) {
// failed index-pattern retrieval will show up as error-message in the layer-toc-entry
return [];
}
}
- getFieldNames() {
+ getFieldNames(): string[] {
return [this._descriptor.geoField];
}
- async getImmutableProperties() {
- let indexPatternTitle = this.getIndexPatternId();
+ async getImmutableProperties(): Promise {
+ let indexPatternName = this.getIndexPatternId();
let geoFieldType = '';
try {
const indexPattern = await this.getIndexPattern();
- indexPatternTitle = indexPattern.title;
+ indexPatternName = indexPattern.title;
const geoField = await this._getGeoField();
geoFieldType = geoField.type;
} catch (error) {
@@ -168,7 +223,7 @@ export class ESSearchSource extends AbstractESSource {
label: i18n.translate('xpack.maps.source.esSearch.indexPatternLabel', {
defaultMessage: `Index pattern`,
}),
- value: indexPatternTitle,
+ value: indexPatternName,
},
{
label: i18n.translate('xpack.maps.source.esSearch.geoFieldLabel', {
@@ -186,8 +241,12 @@ export class ESSearchSource extends AbstractESSource {
}
// Returns sort content for an Elasticsearch search body
- _buildEsSort() {
+ _buildEsSort(): Array> {
const { sortField, sortOrder } = this._descriptor;
+
+ if (!sortField) {
+ throw new Error('Cannot build sort');
+ }
return [
{
[sortField]: {
@@ -197,16 +256,30 @@ export class ESSearchSource extends AbstractESSource {
];
}
- async _getTopHits(layerName, searchFilters, registerCancelCallback) {
+ async _getTopHits(
+ layerName: string,
+ searchFilters: VectorSourceRequestMeta,
+ registerCancelCallback: (callback: () => void) => void
+ ) {
const { topHitsSplitField: topHitsSplitFieldName, topHitsSize } = this._descriptor;
- const indexPattern = await this.getIndexPattern();
+ if (!topHitsSplitFieldName) {
+ throw new Error('Cannot _getTopHits without topHitsSplitField');
+ }
+
+ const indexPattern: IndexPattern = await this.getIndexPattern();
const { docValueFields, sourceOnlyFields, scriptFields } = getDocValueAndSourceFields(
indexPattern,
searchFilters.fieldNames
);
- const topHits = {
+ const topHits: {
+ size: number;
+ script_fields: Record;
+ docvalue_fields: Array;
+ _source?: boolean | { includes: string[] };
+ sort?: Array>;
+ } = {
size: topHitsSize,
script_fields: scriptFields,
docvalue_fields: docValueFields,
@@ -215,6 +288,7 @@ export class ESSearchSource extends AbstractESSource {
if (this._hasSort()) {
topHits.sort = this._buildEsSort();
}
+
if (sourceOnlyFields.length === 0) {
topHits._source = false;
} else {
@@ -223,7 +297,7 @@ export class ESSearchSource extends AbstractESSource {
};
}
- const topHitsSplitField = getField(indexPattern, topHitsSplitFieldName);
+ const topHitsSplitField: IFieldType = getField(indexPattern, topHitsSplitFieldName);
const cardinalityAgg = { precision_threshold: 1 };
const termsAgg = {
size: DEFAULT_MAX_BUCKETS_LIMIT,
@@ -253,13 +327,13 @@ export class ESSearchSource extends AbstractESSource {
requestDescription: 'Elasticsearch document top hits request',
});
- const allHits = [];
+ const allHits: any[] = [];
const entityBuckets = _.get(resp, 'aggregations.entitySplit.buckets', []);
const totalEntities = _.get(resp, 'aggregations.totalEntities.value', 0);
// can not compare entityBuckets.length to totalEntities because totalEntities is an approximate
const areEntitiesTrimmed = entityBuckets.length >= DEFAULT_MAX_BUCKETS_LIMIT;
let areTopHitsTrimmed = false;
- entityBuckets.forEach((entityBucket) => {
+ entityBuckets.forEach((entityBucket: any) => {
const total = _.get(entityBucket, 'entityHits.hits.total', 0);
const hits = _.get(entityBucket, 'entityHits.hits.hits', []);
// Reverse hits list so top documents by sort are drawn on top
@@ -282,7 +356,12 @@ export class ESSearchSource extends AbstractESSource {
// searchFilters.fieldNames contains geo field and any fields needed for styling features
// Performs Elasticsearch search request being careful to pull back only required fields to minimize response size
- async _getSearchHits(layerName, searchFilters, maxResultWindow, registerCancelCallback) {
+ async _getSearchHits(
+ layerName: string,
+ searchFilters: VectorSourceRequestMeta,
+ maxResultWindow: number,
+ registerCancelCallback: (callback: () => void) => void
+ ) {
const indexPattern = await this.getIndexPattern();
const { docValueFields, sourceOnlyFields } = getDocValueAndSourceFields(
@@ -322,23 +401,28 @@ export class ESSearchSource extends AbstractESSource {
};
}
- _isTopHits() {
+ _isTopHits(): boolean {
const { scalingType, topHitsSplitField } = this._descriptor;
return !!(scalingType === SCALING_TYPES.TOP_HITS && topHitsSplitField);
}
- _hasSort() {
+ _hasSort(): boolean {
const { sortField, sortOrder } = this._descriptor;
return !!sortField && !!sortOrder;
}
- async getMaxResultWindow() {
+ async getMaxResultWindow(): Promise {
const indexPattern = await this.getIndexPattern();
const indexSettings = await loadIndexSettings(indexPattern.title);
return indexSettings.maxResultWindow;
}
- async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) {
+ async getGeoJsonWithMeta(
+ layerName: string,
+ searchFilters: VectorSourceRequestMeta,
+ registerCancelCallback: (callback: () => void) => void,
+ isRequestStillActive: () => boolean
+ ): Promise {
const indexPattern = await this.getIndexPattern();
const indexSettings = await loadIndexSettings(indexPattern.title);
@@ -355,7 +439,7 @@ export class ESSearchSource extends AbstractESSource {
const unusedMetaFields = indexPattern.metaFields.filter((metaField) => {
return !['_id', '_index'].includes(metaField);
});
- const flattenHit = (hit) => {
+ const flattenHit = (hit: Record) => {
const properties = indexPattern.flattenHit(hit);
// remove metaFields
unusedMetaFields.forEach((metaField) => {
@@ -375,7 +459,7 @@ export class ESSearchSource extends AbstractESSource {
hits,
flattenHit,
geoField.name,
- geoField.type,
+ geoField.type as ES_GEO_FIELD_TYPE,
epochMillisFields
);
} catch (error) {
@@ -394,11 +478,11 @@ export class ESSearchSource extends AbstractESSource {
};
}
- canFormatFeatureProperties() {
+ canFormatFeatureProperties(): boolean {
return this._tooltipFields.length > 0;
}
- async _loadTooltipProperties(docId, index, indexPattern) {
+ async _loadTooltipProperties(docId: string | number, index: string, indexPattern: IndexPattern) {
if (this._tooltipFields.length === 0) {
return {};
}
@@ -430,7 +514,7 @@ export class ESSearchSource extends AbstractESSource {
}
const properties = indexPattern.flattenHit(hit);
- indexPattern.metaFields.forEach((metaField) => {
+ indexPattern.metaFields.forEach((metaField: string) => {
if (!this._getTooltipPropertyNames().includes(metaField)) {
delete properties[metaField];
}
@@ -438,7 +522,14 @@ export class ESSearchSource extends AbstractESSource {
return properties;
}
- async getTooltipProperties(properties) {
+ _getTooltipPropertyNames(): string[] {
+ return this._tooltipFields.map((field: IField) => field.getName());
+ }
+
+ async getTooltipProperties(properties: GeoJsonProperties): Promise {
+ if (properties === null) {
+ throw new Error('properties cannot be null');
+ }
const indexPattern = await this.getIndexPattern();
const propertyValues = await this._loadTooltipProperties(
properties._id,
@@ -452,25 +543,27 @@ export class ESSearchSource extends AbstractESSource {
return Promise.all(tooltipProperties);
}
- isFilterByMapBounds() {
- if (this._descriptor.scalingType === SCALING_TYPES.CLUSTER) {
+ isFilterByMapBounds(): boolean {
+ if (this._descriptor.scalingType === SCALING_TYPES.CLUSTERS) {
return true;
} else if (this._descriptor.scalingType === SCALING_TYPES.MVT) {
return false;
} else {
- return this._descriptor.filterByMapBounds;
+ return !!this._descriptor.filterByMapBounds;
}
}
- async getLeftJoinFields() {
+ async getLeftJoinFields(): Promise {
const indexPattern = await this.getIndexPattern();
// Left fields are retrieved from _source.
- return getSourceFields(indexPattern.fields).map((field) =>
- this.createField({ fieldName: field.name })
+ return getSourceFields(indexPattern.fields).map(
+ (field): IField => {
+ return this.createField({ fieldName: field.name });
+ }
);
}
- async getSupportedShapeTypes() {
+ async getSupportedShapeTypes(): Promise {
let geoFieldType;
try {
const geoField = await this._getGeoField();
@@ -486,8 +579,10 @@ export class ESSearchSource extends AbstractESSource {
return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
}
- getSourceTooltipContent(sourceDataRequest) {
- const featureCollection = sourceDataRequest ? sourceDataRequest.getData() : null;
+ getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig {
+ const featureCollection: FeatureCollection | null = sourceDataRequest
+ ? (sourceDataRequest.getData() as FeatureCollection)
+ : null;
const meta = sourceDataRequest ? sourceDataRequest.getMeta() : null;
if (!featureCollection || !meta) {
// no tooltip content needed when there is no feature collection or meta
@@ -519,7 +614,7 @@ export class ESSearchSource extends AbstractESSource {
tooltipContent: `${entitiesFoundMsg} ${docsPerEntityMsg}`,
// Used to show trimmed icon in legend
// user only needs to be notified of trimmed results when entities are trimmed
- areResultsTrimmed: meta.areEntitiesTrimmed,
+ areResultsTrimmed: !!meta.areEntitiesTrimmed,
};
}
@@ -542,7 +637,7 @@ export class ESSearchSource extends AbstractESSource {
};
}
- getSyncMeta() {
+ getSyncMeta(): VectorSourceSyncMeta | null {
return {
sortField: this._descriptor.sortField,
sortOrder: this._descriptor.sortOrder,
@@ -552,7 +647,10 @@ export class ESSearchSource extends AbstractESSource {
};
}
- async getPreIndexedShape(properties) {
+ async getPreIndexedShape(properties: GeoJsonProperties): Promise {
+ if (properties === null) {
+ return null;
+ }
const geoField = await this._getGeoField();
return {
index: properties._index, // Can not use index pattern title because it may reference many indices
@@ -561,7 +659,7 @@ export class ESSearchSource extends AbstractESSource {
};
}
- getJoinsDisabledReason() {
+ getJoinsDisabledReason(): string | null {
let reason;
if (this._descriptor.scalingType === SCALING_TYPES.CLUSTERS) {
reason = i18n.translate('xpack.maps.source.esSearch.joinsDisabledReason', {
@@ -577,11 +675,18 @@ export class ESSearchSource extends AbstractESSource {
return reason;
}
- getLayerName() {
+ getLayerName(): string {
return MVT_SOURCE_LAYER_NAME;
}
- async getUrlTemplateWithMeta(searchFilters) {
+ async getUrlTemplateWithMeta(
+ searchFilters: VectorSourceRequestMeta
+ ): Promise<{
+ layerName: string;
+ urlTemplate: string;
+ minSourceZoom: number;
+ maxSourceZoom: number;
+ }> {
const indexPattern = await this.getIndexPattern();
const indexSettings = await loadIndexSettings(indexPattern.title);
@@ -621,7 +726,7 @@ export class ESSearchSource extends AbstractESSource {
layerName: this.getLayerName(),
minSourceZoom: this.getMinZoom(),
maxSourceZoom: this.getMaxZoom(),
- urlTemplate: urlTemplate,
+ urlTemplate,
};
}
}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/load_index_settings.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/load_index_settings.ts
similarity index 79%
rename from x-pack/plugins/maps/public/classes/sources/es_search_source/load_index_settings.js
rename to x-pack/plugins/maps/public/classes/sources/es_search_source/load_index_settings.ts
index d5d24da225232..5f69fa2eeadb3 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_search_source/load_index_settings.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/load_index_settings.ts
@@ -4,20 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { i18n } from '@kbn/i18n';
import {
DEFAULT_MAX_RESULT_WINDOW,
DEFAULT_MAX_INNER_RESULT_WINDOW,
INDEX_SETTINGS_API_PATH,
} from '../../../../common/constants';
import { getHttp, getToasts } from '../../../kibana_services';
-import { i18n } from '@kbn/i18n';
let toastDisplayed = false;
-const indexSettings = new Map();
+const indexSettings = new Map>();
+
+export interface INDEX_SETTINGS {
+ maxResultWindow: number;
+ maxInnerResultWindow: number;
+}
-export async function loadIndexSettings(indexPatternTitle) {
+export async function loadIndexSettings(indexPatternTitle: string): Promise {
if (indexSettings.has(indexPatternTitle)) {
- return indexSettings.get(indexPatternTitle);
+ return indexSettings.get(indexPatternTitle)!;
}
const fetchPromise = fetchIndexSettings(indexPatternTitle);
@@ -25,7 +30,7 @@ export async function loadIndexSettings(indexPatternTitle) {
return fetchPromise;
}
-async function fetchIndexSettings(indexPatternTitle) {
+async function fetchIndexSettings(indexPatternTitle: string): Promise {
const http = getHttp();
const toasts = getToasts();
try {
@@ -50,6 +55,7 @@ async function fetchIndexSettings(indexPatternTitle) {
toastDisplayed = true;
toasts.addWarning(warningMsg);
}
+ // eslint-disable-next-line no-console
console.warn(warningMsg);
return {
maxResultWindow: DEFAULT_MAX_RESULT_WINDOW,
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js
index 735510102e25e..eef802f1e19ce 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js
@@ -18,10 +18,10 @@ import {
getSourceFields,
supportsGeoTileAgg,
} from '../../../index_pattern_util';
-import { SORT_ORDER } from '../../../../common/constants';
+import { SortDirection, indexPatterns } from '../../../../../../../src/plugins/data/public';
import { ESDocField } from '../../fields/es_doc_field';
import { FormattedMessage } from '@kbn/i18n/react';
-import { indexPatterns } from '../../../../../../../src/plugins/data/public';
+
import { ScalingForm } from './scaling_form';
export class UpdateSourceEditor extends Component {
@@ -183,13 +183,13 @@ export class UpdateSourceEditor extends Component {
text: i18n.translate('xpack.maps.source.esSearch.ascendingLabel', {
defaultMessage: 'ascending',
}),
- value: SORT_ORDER.ASC,
+ value: SortDirection.asc,
},
{
text: i18n.translate('xpack.maps.source.esSearch.descendingLabel', {
defaultMessage: 'descending',
}),
- value: SORT_ORDER.DESC,
+ value: SortDirection.desc,
},
]}
value={this.props.sortOrder}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.d.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.d.ts
deleted file mode 100644
index c11b6f0853cc7..0000000000000
--- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.d.ts
+++ /dev/null
@@ -1,86 +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 { AbstractVectorSource } from '../vector_source';
-import { IVectorSource } from '../vector_source';
-import { TimeRange } from '../../../../../../../src/plugins/data/common';
-import { IndexPattern, ISearchSource } from '../../../../../../../src/plugins/data/public';
-import {
- DynamicStylePropertyOptions,
- MapQuery,
- VectorSourceRequestMeta,
-} from '../../../../common/descriptor_types';
-import { IVectorStyle } from '../../styles/vector/vector_style';
-import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
-
-export interface IESSource extends IVectorSource {
- getId(): string;
- getIndexPattern(): Promise;
- getIndexPatternId(): string;
- getGeoFieldName(): string;
- getMaxResultWindow(): Promise;
- makeSearchSource(
- searchFilters: VectorSourceRequestMeta,
- limit: number,
- initialSearchContext?: object
- ): Promise;
- loadStylePropsMeta({
- layerName,
- style,
- dynamicStyleProps,
- registerCancelCallback,
- sourceQuery,
- timeFilters,
- }: {
- layerName: string;
- style: IVectorStyle;
- dynamicStyleProps: Array>;
- registerCancelCallback: (callback: () => void) => void;
- sourceQuery?: MapQuery;
- timeFilters: TimeRange;
- }): Promise;
-}
-
-export class AbstractESSource extends AbstractVectorSource implements IESSource {
- getId(): string;
- getIndexPattern(): Promise;
- getIndexPatternId(): string;
- getGeoFieldName(): string;
- getMaxResultWindow(): Promise;
- makeSearchSource(
- searchFilters: VectorSourceRequestMeta,
- limit: number,
- initialSearchContext?: object
- ): Promise;
- loadStylePropsMeta({
- layerName,
- style,
- dynamicStyleProps,
- registerCancelCallback,
- sourceQuery,
- timeFilters,
- }: {
- layerName: string;
- style: IVectorStyle;
- dynamicStyleProps: Array>;
- registerCancelCallback: (callback: () => void) => void;
- sourceQuery?: MapQuery;
- timeFilters: TimeRange;
- }): Promise;
- _runEsQuery: ({
- requestId,
- requestName,
- requestDescription,
- searchSource,
- registerCancelCallback,
- }: {
- requestId: string;
- requestName: string;
- requestDescription: string;
- searchSource: ISearchSource;
- registerCancelCallback: () => void;
- }) => Promise;
-}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts
similarity index 51%
rename from x-pack/plugins/maps/public/classes/sources/es_source/es_source.js
rename to x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts
index 0c8cb5f514247..68b6b131978ea 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts
@@ -4,7 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { AbstractVectorSource } from '../vector_source';
+import { i18n } from '@kbn/i18n';
+import uuid from 'uuid/v4';
+import { Filter, IFieldType, IndexPattern, ISearchSource } from 'src/plugins/data/public';
+import { AbstractVectorSource, BoundsFilters } from '../vector_source';
import {
getAutocompleteService,
getIndexPatternService,
@@ -12,62 +15,122 @@ import {
getSearchService,
} from '../../../kibana_services';
import { createExtentFilter } from '../../../../common/elasticsearch_util';
-import _ from 'lodash';
-import { i18n } from '@kbn/i18n';
-import uuid from 'uuid/v4';
-
import { copyPersistentState } from '../../../reducers/util';
import { DataRequestAbortError } from '../../util/data_request';
import { expandToTileBoundaries } from '../../../../common/geo_tile_utils';
import { search } from '../../../../../../../src/plugins/data/public';
+import { IVectorSource } from '../vector_source';
+import { TimeRange } from '../../../../../../../src/plugins/data/common';
+import {
+ AbstractESSourceDescriptor,
+ AbstractSourceDescriptor,
+ DynamicStylePropertyOptions,
+ MapExtent,
+ MapQuery,
+ VectorJoinSourceRequestMeta,
+ VectorSourceRequestMeta,
+} from '../../../../common/descriptor_types';
+import { IVectorStyle } from '../../styles/vector/vector_style';
+import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
+import { IField } from '../../fields/field';
+import { ES_GEO_FIELD_TYPE, FieldFormatter } from '../../../../common/constants';
+import {
+ Adapters,
+ RequestResponder,
+} from '../../../../../../../src/plugins/inspector/common/adapters';
+import { isValidStringConfig } from '../../util/valid_string_config';
+
+export interface IESSource extends IVectorSource {
+ isESSource(): true;
+ getId(): string;
+ getIndexPattern(): Promise;
+ getIndexPatternId(): string;
+ getGeoFieldName(): string;
+ loadStylePropsMeta({
+ layerName,
+ style,
+ dynamicStyleProps,
+ registerCancelCallback,
+ sourceQuery,
+ timeFilters,
+ }: {
+ layerName: string;
+ style: IVectorStyle;
+ dynamicStyleProps: Array>;
+ registerCancelCallback: (callback: () => void) => void;
+ sourceQuery?: MapQuery;
+ timeFilters: TimeRange;
+ }): Promise;
+}
-export class AbstractESSource extends AbstractVectorSource {
- constructor(descriptor, inspectorAdapters) {
- super(
- {
- ...descriptor,
- applyGlobalQuery: _.get(descriptor, 'applyGlobalQuery', true),
- },
- inspectorAdapters
- );
+export class AbstractESSource extends AbstractVectorSource implements IESSource {
+ indexPattern?: IndexPattern;
+
+ readonly _descriptor: AbstractESSourceDescriptor;
+
+ static createDescriptor(
+ descriptor: Partial
+ ): AbstractESSourceDescriptor {
+ if (!isValidStringConfig(descriptor.indexPatternId)) {
+ throw new Error(
+ 'Cannot create AbstractESSourceDescriptor when indexPatternId is not provided'
+ );
+ }
+ return {
+ ...descriptor,
+ id: isValidStringConfig(descriptor.id) ? descriptor.id! : uuid(),
+ type: isValidStringConfig(descriptor.type) ? descriptor.type! : '',
+ indexPatternId: descriptor.indexPatternId!,
+ applyGlobalQuery:
+ // backfill old _.get usage
+ typeof descriptor.applyGlobalQuery !== 'undefined' ? !!descriptor.applyGlobalQuery : true,
+ };
+ }
+
+ constructor(descriptor: AbstractESSourceDescriptor, inspectorAdapters?: Adapters) {
+ super(AbstractESSource.createDescriptor(descriptor), inspectorAdapters);
+ this._descriptor = descriptor;
}
- getId() {
+ getId(): string {
return this._descriptor.id;
}
- isFieldAware() {
+ isFieldAware(): boolean {
return true;
}
- isRefreshTimerAware() {
+ isRefreshTimerAware(): boolean {
return true;
}
- isQueryAware() {
+ isQueryAware(): boolean {
return true;
}
- getIndexPatternIds() {
+ getIndexPatternIds(): string[] {
return [this.getIndexPatternId()];
}
- getQueryableIndexPatternIds() {
+ getQueryableIndexPatternIds(): string[] {
if (this.getApplyGlobalQuery()) {
return [this.getIndexPatternId()];
}
return [];
}
- isESSource() {
+ isESSource(): true {
return true;
}
destroy() {
- this._inspectorAdapters.requests.resetRequest(this.getId());
+ const inspectorAdapters = this.getInspectorAdapters();
+ if (inspectorAdapters) {
+ inspectorAdapters.requests.resetRequest(this.getId());
+ }
}
- cloneDescriptor() {
+ cloneDescriptor(): AbstractSourceDescriptor {
const clonedDescriptor = copyPersistentState(this._descriptor);
// id used as uuid to track requests in inspector
clonedDescriptor.id = uuid();
@@ -80,26 +143,45 @@ export class AbstractESSource extends AbstractVectorSource {
requestDescription,
searchSource,
registerCancelCallback,
- }) {
+ }: {
+ requestId: string;
+ requestName: string;
+ requestDescription: string;
+ searchSource: ISearchSource;
+ registerCancelCallback: (callback: () => void) => void;
+ }): Promise {
const abortController = new AbortController();
registerCancelCallback(() => abortController.abort());
- const inspectorRequest = this._inspectorAdapters.requests.start(requestName, {
- id: requestId,
- description: requestDescription,
- });
+ const inspectorAdapters = this.getInspectorAdapters();
+ let inspectorRequest: RequestResponder | undefined;
+ if (inspectorAdapters) {
+ inspectorRequest = inspectorAdapters.requests.start(requestName, {
+ id: requestId,
+ description: requestDescription,
+ });
+ }
+
let resp;
try {
- inspectorRequest.stats(search.getRequestInspectorStats(searchSource));
- searchSource.getSearchRequestBody().then((body) => {
- inspectorRequest.json(body);
- });
+ if (inspectorRequest) {
+ const requestStats = search.getRequestInspectorStats(searchSource);
+ inspectorRequest.stats(requestStats);
+ searchSource.getSearchRequestBody().then((body) => {
+ if (inspectorRequest) {
+ inspectorRequest.json(body);
+ }
+ });
+ }
resp = await searchSource.fetch({ abortSignal: abortController.signal });
- inspectorRequest
- .stats(search.getResponseInspectorStats(resp, searchSource))
- .ok({ json: resp });
+ if (inspectorRequest) {
+ const responseStats = search.getResponseInspectorStats(resp, searchSource);
+ inspectorRequest.stats(responseStats).ok({ json: resp });
+ }
} catch (error) {
- inspectorRequest.error({ error });
+ if (inspectorRequest) {
+ inspectorRequest.error(error);
+ }
if (error.name === 'AbortError') {
throw new DataRequestAbortError();
}
@@ -115,22 +197,40 @@ export class AbstractESSource extends AbstractVectorSource {
return resp;
}
- async makeSearchSource(searchFilters, limit, initialSearchContext) {
+ async makeSearchSource(
+ searchFilters: VectorSourceRequestMeta | VectorJoinSourceRequestMeta | BoundsFilters,
+ limit: number,
+ initialSearchContext?: object
+ ): Promise {
const indexPattern = await this.getIndexPattern();
const isTimeAware = await this.isTimeAware();
- const applyGlobalQuery = _.get(searchFilters, 'applyGlobalQuery', true);
- const globalFilters = applyGlobalQuery ? searchFilters.filters : [];
- const allFilters = [...globalFilters];
- if (this.isFilterByMapBounds() && searchFilters.buffer) {
- //buffer can be empty
+ const applyGlobalQuery =
+ typeof searchFilters.applyGlobalQuery === 'boolean' ? searchFilters.applyGlobalQuery : true;
+ const globalFilters: Filter[] = applyGlobalQuery ? searchFilters.filters : [];
+ const allFilters: Filter[] = [...globalFilters];
+ if (this.isFilterByMapBounds() && 'buffer' in searchFilters && searchFilters.buffer) {
+ // buffer can be empty
const geoField = await this._getGeoField();
- const buffer = this.isGeoGridPrecisionAware()
- ? expandToTileBoundaries(searchFilters.buffer, searchFilters.geogridPrecision)
- : searchFilters.buffer;
- allFilters.push(createExtentFilter(buffer, geoField.name, geoField.type));
+ const buffer: MapExtent =
+ this.isGeoGridPrecisionAware() &&
+ 'geogridPrecision' in searchFilters &&
+ typeof searchFilters.geogridPrecision === 'number'
+ ? expandToTileBoundaries(searchFilters.buffer, searchFilters.geogridPrecision)
+ : searchFilters.buffer;
+ const extentFilter = createExtentFilter(
+ buffer,
+ geoField.name,
+ geoField.type as ES_GEO_FIELD_TYPE
+ );
+
+ // @ts-expect-error
+ allFilters.push(extentFilter);
}
if (isTimeAware) {
- allFilters.push(getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters));
+ const filter = getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters);
+ if (filter) {
+ allFilters.push(filter);
+ }
}
const searchService = getSearchService();
const searchSource = await searchService.searchSource.create(initialSearchContext);
@@ -153,7 +253,10 @@ export class AbstractESSource extends AbstractVectorSource {
return searchSource;
}
- async getBoundsForFilters(boundsFilters, registerCancelCallback) {
+ async getBoundsForFilters(
+ boundsFilters: BoundsFilters,
+ registerCancelCallback: (callback: () => void) => void
+ ): Promise {
const searchSource = await this.makeSearchSource(boundsFilters, 0);
searchSource.setField('aggs', {
fitToBounds: {
@@ -184,14 +287,14 @@ export class AbstractESSource extends AbstractVectorSource {
const minLon = esBounds.top_left.lon;
const maxLon = esBounds.bottom_right.lon;
return {
- minLon: minLon > maxLon ? minLon - 360 : minLon, //fixes an ES bbox to straddle dateline
+ minLon: minLon > maxLon ? minLon - 360 : minLon, // fixes an ES bbox to straddle dateline
maxLon,
minLat: esBounds.bottom_right.lat,
maxLat: esBounds.top_left.lat,
};
}
- async isTimeAware() {
+ async isTimeAware(): Promise {
try {
const indexPattern = await this.getIndexPattern();
const timeField = indexPattern.timeFieldName;
@@ -201,15 +304,19 @@ export class AbstractESSource extends AbstractVectorSource {
}
}
- getIndexPatternId() {
+ getIndexPatternId(): string {
return this._descriptor.indexPatternId;
}
- getGeoFieldName() {
+ getGeoFieldName(): string {
+ if (!this._descriptor.geoField) {
+ throw new Error('Should not call');
+ }
return this._descriptor.geoField;
}
- async getIndexPattern() {
+ async getIndexPattern(): Promise {
+ // Do we need this cache? Doesn't the IndexPatternService take care of this?
if (this.indexPattern) {
return this.indexPattern;
}
@@ -227,16 +334,16 @@ export class AbstractESSource extends AbstractVectorSource {
}
}
- async supportsFitToBounds() {
+ async supportsFitToBounds(): Promise {
try {
const geoField = await this._getGeoField();
- return geoField.aggregatable;
+ return !!geoField.aggregatable;
} catch (error) {
return false;
}
}
- async _getGeoField() {
+ async _getGeoField(): Promise {
const indexPattern = await this.getIndexPattern();
const geoField = indexPattern.fields.getByName(this.getGeoFieldName());
if (!geoField) {
@@ -250,7 +357,7 @@ export class AbstractESSource extends AbstractVectorSource {
return geoField;
}
- async getDisplayName() {
+ async getDisplayName(): Promise {
try {
const indexPattern = await this.getIndexPattern();
return indexPattern.title;
@@ -260,15 +367,11 @@ export class AbstractESSource extends AbstractVectorSource {
}
}
- isBoundsAware() {
+ isBoundsAware(): boolean {
return true;
}
- getId() {
- return this._descriptor.id;
- }
-
- async createFieldFormatter(field) {
+ async createFieldFormatter(field: IField): Promise {
let indexPattern;
try {
indexPattern = await this.getIndexPattern();
@@ -291,15 +394,25 @@ export class AbstractESSource extends AbstractVectorSource {
registerCancelCallback,
sourceQuery,
timeFilters,
- }) {
+ }: {
+ layerName: string;
+ style: IVectorStyle;
+ dynamicStyleProps: Array>;
+ registerCancelCallback: (callback: () => void) => void;
+ sourceQuery?: MapQuery;
+ timeFilters: TimeRange;
+ }): Promise {
const promises = dynamicStyleProps.map((dynamicStyleProp) => {
return dynamicStyleProp.getFieldMetaRequest();
});
const fieldAggRequests = await Promise.all(promises);
- const aggs = fieldAggRequests.reduce((aggs, fieldAggRequest) => {
- return fieldAggRequest ? { ...aggs, ...fieldAggRequest } : aggs;
- }, {});
+ const allAggs: Record = fieldAggRequests.reduce(
+ (aggs: Record, fieldAggRequest: unknown | null) => {
+ return fieldAggRequest ? { ...aggs, ...(fieldAggRequest as Record) } : aggs;
+ },
+ {}
+ );
const indexPattern = await this.getIndexPattern();
const searchService = getSearchService();
@@ -307,12 +420,15 @@ export class AbstractESSource extends AbstractVectorSource {
searchSource.setField('index', indexPattern);
searchSource.setField('size', 0);
- searchSource.setField('aggs', aggs);
+ searchSource.setField('aggs', allAggs);
if (sourceQuery) {
searchSource.setField('query', sourceQuery);
}
if (style.isTimeAware() && (await this.isTimeAware())) {
- searchSource.setField('filter', [getTimeFilter().createFilter(indexPattern, timeFilters)]);
+ const timeFilter = getTimeFilter().createFilter(indexPattern, timeFilters);
+ if (timeFilter) {
+ searchSource.setField('filter', [timeFilter]);
+ }
}
const resp = await this._runEsQuery({
@@ -335,15 +451,17 @@ export class AbstractESSource extends AbstractVectorSource {
return resp.aggregations;
}
- getValueSuggestions = async (field, query) => {
+ getValueSuggestions = async (field: IField, query: string): Promise => {
try {
const indexPattern = await this.getIndexPattern();
+ const indexPatternField = indexPattern.fields.getByName(field.getRootName())!;
return await getAutocompleteService().getValueSuggestions({
indexPattern,
- field: indexPattern.fields.getByName(field.getRootName()),
+ field: indexPatternField,
query,
});
} catch (error) {
+ // eslint-disable-next-line no-console
console.warn(
`Unable to fetch suggestions for field: ${field.getRootName()}, query: ${query}, error: ${
error.message
diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.d.ts b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.d.ts
deleted file mode 100644
index ef1ada8da8289..0000000000000
--- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.d.ts
+++ /dev/null
@@ -1,22 +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 { MapQuery, VectorJoinSourceRequestMeta } from '../../../../common/descriptor_types';
-import { IField } from '../../fields/field';
-import { IESAggSource } from '../es_agg_source';
-import { PropertiesMap } from '../../joins/join';
-
-export interface IESTermSource extends IESAggSource {
- getTermField: () => IField;
- hasCompleteConfig: () => boolean;
- getWhereQuery: () => MapQuery;
- getPropertiesMap: (
- searchFilters: VectorJoinSourceRequestMeta,
- leftSourceName: string,
- leftFieldName: string,
- registerCancelCallback: (callback: () => void) => void
- ) => PropertiesMap;
-}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js
index 060096157f578..22ef4cc8b373a 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.test.js
@@ -34,6 +34,7 @@ describe('getMetricFields', () => {
id: '1234',
indexPatternTitle: indexPatternTitle,
term: termFieldName,
+ indexPatternId: 'foobar',
});
const metrics = source.getMetricFields();
expect(metrics[0].getName()).toEqual('__kbnjoin__count__1234');
@@ -46,6 +47,7 @@ describe('getMetricFields', () => {
indexPatternTitle: indexPatternTitle,
term: termFieldName,
metrics: metricExamples,
+ indexPatternId: 'foobar',
});
const metrics = source.getMetricFields();
expect(metrics[0].getName()).toEqual('__kbnjoin__sum_of_myFieldGettingSummed__1234');
diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts
similarity index 65%
rename from x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js
rename to x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts
index ff52dccdd2ef4..3220253436168 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts
@@ -5,8 +5,8 @@
*/
import _ from 'lodash';
-
import { i18n } from '@kbn/i18n';
+import { ISearchSource, Query } from 'src/plugins/data/public';
import {
AGG_TYPE,
DEFAULT_MAX_BUCKETS_LIMIT,
@@ -20,15 +20,22 @@ import {
getField,
addFieldToDSL,
extractPropertiesFromBucket,
+ BucketProperties,
} from '../../../../common/elasticsearch_util';
+import {
+ ESTermSourceDescriptor,
+ VectorJoinSourceRequestMeta,
+} from '../../../../common/descriptor_types';
+import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
+import { PropertiesMap } from '../../../../common/elasticsearch_util';
const TERMS_AGG_NAME = 'join';
-
const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count'];
-export function extractPropertiesMap(rawEsData, countPropertyName) {
- const propertiesMap = new Map();
- _.get(rawEsData, ['aggregations', TERMS_AGG_NAME, 'buckets'], []).forEach((termBucket) => {
+export function extractPropertiesMap(rawEsData: any, countPropertyName: string): PropertiesMap {
+ const propertiesMap: PropertiesMap = new Map();
+ const buckets: any[] = _.get(rawEsData, ['aggregations', TERMS_AGG_NAME, 'buckets'], []);
+ buckets.forEach((termBucket: any) => {
const properties = extractPropertiesFromBucket(termBucket, TERMS_BUCKET_KEYS_TO_IGNORE);
if (countPropertyName) {
properties[countPropertyName] = termBucket.doc_count;
@@ -41,37 +48,36 @@ export function extractPropertiesMap(rawEsData, countPropertyName) {
export class ESTermSource extends AbstractESAggSource {
static type = SOURCE_TYPES.ES_TERM_SOURCE;
- constructor(descriptor, inspectorAdapters) {
- super(descriptor, inspectorAdapters);
+ private readonly _termField: ESDocField;
+ readonly _descriptor: ESTermSourceDescriptor;
+
+ constructor(descriptor: ESTermSourceDescriptor, inspectorAdapters: Adapters) {
+ super(AbstractESAggSource.createDescriptor(descriptor), inspectorAdapters);
+ this._descriptor = descriptor;
this._termField = new ESDocField({
- fieldName: descriptor.term,
+ fieldName: this._descriptor.term,
source: this,
origin: this.getOriginForField(),
});
}
- static renderEditor({}) {
- //no need to localize. this editor is never rendered.
- return `editor details
`;
- }
-
hasCompleteConfig() {
return _.has(this._descriptor, 'indexPatternId') && _.has(this._descriptor, 'term');
}
- getTermField() {
+ getTermField(): ESDocField {
return this._termField;
}
- getOriginForField() {
+ getOriginForField(): FIELD_ORIGIN {
return FIELD_ORIGIN.JOIN;
}
- getWhereQuery() {
+ getWhereQuery(): Query | undefined {
return this._descriptor.whereQuery;
}
- getAggKey(aggType, fieldName) {
+ getAggKey(aggType: AGG_TYPE, fieldName?: string): string {
return getJoinAggKey({
aggType,
aggFieldName: fieldName,
@@ -79,7 +85,7 @@ export class ESTermSource extends AbstractESAggSource {
});
}
- getAggLabel(aggType, fieldName) {
+ getAggLabel(aggType: AGG_TYPE, fieldName: string) {
return aggType === AGG_TYPE.COUNT
? i18n.translate('xpack.maps.source.esJoin.countLabel', {
defaultMessage: `Count of {indexPatternTitle}`,
@@ -88,13 +94,18 @@ export class ESTermSource extends AbstractESAggSource {
: super.getAggLabel(aggType, fieldName);
}
- async getPropertiesMap(searchFilters, leftSourceName, leftFieldName, registerCancelCallback) {
+ async getPropertiesMap(
+ searchFilters: VectorJoinSourceRequestMeta,
+ leftSourceName: string,
+ leftFieldName: string,
+ registerCancelCallback: (callback: () => void) => void
+ ): Promise {
if (!this.hasCompleteConfig()) {
- return [];
+ return new Map();
}
const indexPattern = await this.getIndexPattern();
- const searchSource = await this.makeSearchSource(searchFilters, 0);
+ const searchSource: ISearchSource = await this.makeSearchSource(searchFilters, 0);
const termsField = getField(indexPattern, this._termField.getName());
const termsAgg = { size: DEFAULT_MAX_BUCKETS_LIMIT };
searchSource.setField('aggs', {
@@ -122,16 +133,16 @@ export class ESTermSource extends AbstractESAggSource {
return extractPropertiesMap(rawEsData, countPropertyName);
}
- isFilterByMapBounds() {
+ isFilterByMapBounds(): boolean {
return false;
}
- async getDisplayName() {
- //no need to localize. this is never rendered.
+ async getDisplayName(): Promise {
+ // no need to localize. this is never rendered.
return `es_table ${this.getIndexPatternId()}`;
}
- getFieldNames() {
+ getFieldNames(): string[] {
return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName());
}
}
diff --git a/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts b/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts
index 336a947e9fe4f..6172405152739 100644
--- a/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts
+++ b/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file_source.ts
@@ -5,10 +5,11 @@
*/
import { Feature, FeatureCollection } from 'geojson';
-import { AbstractVectorSource } from '../vector_source';
+import { AbstractVectorSource, GeoJsonWithMeta } from '../vector_source';
import { EMPTY_FEATURE_COLLECTION, SOURCE_TYPES } from '../../../../common/constants';
import { GeojsonFileSourceDescriptor } from '../../../../common/descriptor_types';
import { registerSource } from '../source_registry';
+import { IField } from '../../fields/field';
function getFeatureCollection(geoJson: Feature | FeatureCollection | null): FeatureCollection {
if (!geoJson) {
@@ -30,26 +31,28 @@ function getFeatureCollection(geoJson: Feature | FeatureCollection | null): Feat
}
export class GeojsonFileSource extends AbstractVectorSource {
- static type = SOURCE_TYPES.GEOJSON_FILE;
-
static createDescriptor(
geoJson: Feature | FeatureCollection | null,
name: string
): GeojsonFileSourceDescriptor {
return {
- type: GeojsonFileSource.type,
+ type: SOURCE_TYPES.GEOJSON_FILE,
__featureCollection: getFeatureCollection(geoJson),
name,
};
}
- async getGeoJsonWithMeta() {
+ async getGeoJsonWithMeta(): Promise {
return {
data: (this._descriptor as GeojsonFileSourceDescriptor).__featureCollection,
meta: {},
};
}
+ createField({ fieldName }: { fieldName: string }): IField {
+ throw new Error('Not implemented');
+ }
+
async getDisplayName() {
return (this._descriptor as GeojsonFileSourceDescriptor).name;
}
diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx
index c8a1c346646e0..f8a311429d3dc 100644
--- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx
@@ -26,7 +26,7 @@ export const kibanaRegionMapLayerWizardConfig: LayerWizard = {
}),
icon: 'logoKibana',
renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => {
- const onSourceConfigChange = (sourceConfig: unknown) => {
+ const onSourceConfigChange = (sourceConfig: { name: string }) => {
const sourceDescriptor = KibanaRegionmapSource.createDescriptor(sourceConfig);
const layerDescriptor = VectorLayer.createDescriptor({ sourceDescriptor }, mapColors);
previewLayers([layerDescriptor]);
diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts
deleted file mode 100644
index db67001dcd85a..0000000000000
--- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts
+++ /dev/null
@@ -1,15 +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 { AbstractVectorSource, IVectorSource } from '../vector_source';
-
-export interface IKibanaRegionSource extends IVectorSource {
- getVectorFileMeta(): Promise;
-}
-
-export class KibanaRegionSource extends AbstractVectorSource implements IKibanaRegionSource {
- getVectorFileMeta(): Promise;
-}
diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.js b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts
similarity index 55%
rename from x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.js
rename to x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts
index d937edb4ed362..bf39d78a4784f 100644
--- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts
@@ -4,29 +4,38 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { AbstractVectorSource } from '../vector_source';
-import { getKibanaRegionList } from '../../../meta';
import { i18n } from '@kbn/i18n';
+import { AbstractVectorSource, GeoJsonWithMeta } from '../vector_source';
+import { getKibanaRegionList } from '../../../meta';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
-import { FIELD_ORIGIN, SOURCE_TYPES } from '../../../../common/constants';
+import { FIELD_ORIGIN, FORMAT_TYPE, SOURCE_TYPES } from '../../../../common/constants';
import { KibanaRegionField } from '../../fields/kibana_region_field';
import { registerSource } from '../source_registry';
+import { KibanaRegionmapSourceDescriptor } from '../../../../common/descriptor_types/source_descriptor_types';
+import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
+import { IField } from '../../fields/field';
+import { LayerConfig } from '../../../../../../../src/plugins/region_map/config';
export const sourceTitle = i18n.translate('xpack.maps.source.kbnRegionMapTitle', {
defaultMessage: 'Configured GeoJSON',
});
export class KibanaRegionmapSource extends AbstractVectorSource {
- static type = SOURCE_TYPES.REGIONMAP_FILE;
+ readonly _descriptor: KibanaRegionmapSourceDescriptor;
- static createDescriptor({ name }) {
+ static createDescriptor({ name }: { name: string }): KibanaRegionmapSourceDescriptor {
return {
- type: KibanaRegionmapSource.type,
- name: name,
+ type: SOURCE_TYPES.REGIONMAP_FILE,
+ name,
};
}
- createField({ fieldName }) {
+ constructor(descriptor: KibanaRegionmapSourceDescriptor, inspectorAdapters?: Adapters) {
+ super(descriptor, inspectorAdapters);
+ this._descriptor = descriptor;
+ }
+
+ createField({ fieldName }: { fieldName: string }): KibanaRegionField {
return new KibanaRegionField({
fieldName,
source: this,
@@ -49,10 +58,12 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
];
}
- async getVectorFileMeta() {
- const regionList = getKibanaRegionList();
- const meta = regionList.find((source) => source.name === this._descriptor.name);
- if (!meta) {
+ async getVectorFileMeta(): Promise {
+ const regionList: LayerConfig[] = getKibanaRegionList();
+ const layerConfig: LayerConfig | undefined = regionList.find(
+ (regionConfig: LayerConfig) => regionConfig.name === this._descriptor.name
+ );
+ if (!layerConfig) {
throw new Error(
i18n.translate('xpack.maps.source.kbnRegionMap.noConfigErrorMessage', {
defaultMessage: `Unable to find map.regionmap configuration for {name}`,
@@ -62,13 +73,13 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
})
);
}
- return meta;
+ return layerConfig;
}
- async getGeoJsonWithMeta() {
+ async getGeoJsonWithMeta(): Promise {
const vectorFileMeta = await this.getVectorFileMeta();
const featureCollection = await AbstractVectorSource.getGeoJson({
- format: vectorFileMeta.format.type,
+ format: vectorFileMeta.format.type as FORMAT_TYPE,
featureCollectionPath: vectorFileMeta.meta.feature_collection_path,
fetchUrl: vectorFileMeta.url,
});
@@ -78,12 +89,16 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
};
}
- async getLeftJoinFields() {
- const vectorFileMeta = await this.getVectorFileMeta();
- return vectorFileMeta.fields.map((f) => this.createField({ fieldName: f.name }));
+ async getLeftJoinFields(): Promise {
+ const vectorFileMeta: LayerConfig = await this.getVectorFileMeta();
+ return vectorFileMeta.fields.map(
+ (field): KibanaRegionField => {
+ return this.createField({ fieldName: field.name });
+ }
+ );
}
- async getDisplayName() {
+ async getDisplayName(): Promise {
return this._descriptor.name;
}
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx
index 440f0cb4457e8..6390626b006b4 100644
--- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx
@@ -28,6 +28,7 @@ import {
import { MVTField } from '../../fields/mvt_field';
import { UpdateSourceEditor } from './update_source_editor';
import { ITooltipProperty, TooltipProperty } from '../../tooltips/tooltip_property';
+import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
export const sourceTitle = i18n.translate(
'xpack.maps.source.MVTSingleLayerVectorSource.sourceTitle',
@@ -66,7 +67,7 @@ export class MVTSingleLayerVectorSource
constructor(
sourceDescriptor: TiledSingleLayerVectorSourceDescriptor,
- inspectorAdapters?: object
+ inspectorAdapters?: Adapters
) {
super(sourceDescriptor, inspectorAdapters);
this._descriptor = MVTSingleLayerVectorSource.createDescriptor(sourceDescriptor);
@@ -165,22 +166,22 @@ export class MVTSingleLayerVectorSource
return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
}
- canFormatFeatureProperties() {
+ canFormatFeatureProperties(): boolean {
return !!this._tooltipFields.length;
}
- getMinZoom() {
+ getMinZoom(): number {
return this._descriptor.minSourceZoom;
}
- getMaxZoom() {
+ getMaxZoom(): number {
return this._descriptor.maxSourceZoom;
}
- getBoundsForFilters(
+ async getBoundsForFilters(
boundsFilters: BoundsFilters,
registerCancelCallback: (callback: () => void) => void
- ): MapExtent | null {
+ ): Promise {
return null;
}
diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts
index c4fb5178c0b56..f24ec012836b6 100644
--- a/x-pack/plugins/maps/public/classes/sources/source.ts
+++ b/x-pack/plugins/maps/public/classes/sources/source.ts
@@ -9,6 +9,7 @@
import { ReactElement } from 'react';
import { Adapters } from 'src/plugins/inspector/public';
+import { GeoJsonProperties } from 'geojson';
import { copyPersistentState } from '../../reducers/util';
import { IField } from '../fields/field';
@@ -62,7 +63,7 @@ export interface ISource {
getIndexPatternIds(): string[];
getQueryableIndexPatternIds(): string[];
getGeoGridPrecision(zoom: number): number;
- getPreIndexedShape(): Promise;
+ getPreIndexedShape(properties: GeoJsonProperties): Promise;
createFieldFormatter(field: IField): Promise;
getValueSuggestions(field: IField, query: string): Promise;
getMinZoom(): number;
@@ -72,7 +73,7 @@ export interface ISource {
export class AbstractSource implements ISource {
readonly _descriptor: AbstractSourceDescriptor;
- readonly _inspectorAdapters?: Adapters | undefined;
+ private readonly _inspectorAdapters?: Adapters;
constructor(descriptor: AbstractSourceDescriptor, inspectorAdapters?: Adapters) {
this._descriptor = descriptor;
@@ -153,7 +154,7 @@ export class AbstractSource implements ISource {
return false;
}
- getJoinsDisabledReason() {
+ getJoinsDisabledReason(): string | null {
return null;
}
@@ -162,7 +163,7 @@ export class AbstractSource implements ISource {
}
// Returns geo_shape indexed_shape context for spatial quering by pre-indexed shapes
- async getPreIndexedShape(/* properties */): Promise {
+ async getPreIndexedShape(properties: GeoJsonProperties): Promise {
return null;
}
@@ -183,11 +184,11 @@ export class AbstractSource implements ISource {
return false;
}
- getMinZoom() {
+ getMinZoom(): number {
return MIN_ZOOM;
}
- getMaxZoom() {
+ getMaxZoom(): number {
return MAX_ZOOM;
}
diff --git a/x-pack/plugins/maps/public/classes/sources/source_registry.ts b/x-pack/plugins/maps/public/classes/sources/source_registry.ts
index 462624dfa6ec9..2bf7e84850693 100644
--- a/x-pack/plugins/maps/public/classes/sources/source_registry.ts
+++ b/x-pack/plugins/maps/public/classes/sources/source_registry.ts
@@ -6,11 +6,12 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import { ISource } from './source';
+import { Adapters } from '../../../../../../src/plugins/inspector/common/adapters';
export type SourceRegistryEntry = {
ConstructorFunction: new (
sourceDescriptor: any, // this is the source-descriptor that corresponds specifically to the particular ISource instance
- inspectorAdapters?: object
+ inspectorAdapters?: Adapters
) => ISource;
type: string;
};
diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts
deleted file mode 100644
index 7bf1db43c2871..0000000000000
--- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts
+++ /dev/null
@@ -1,108 +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.
- */
-/* eslint-disable @typescript-eslint/consistent-type-definitions */
-
-import { FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
-import { Filter, TimeRange } from 'src/plugins/data/public';
-import { AbstractSource, ISource } from '../source';
-import { IField } from '../../fields/field';
-import {
- ESSearchSourceResponseMeta,
- MapExtent,
- MapFilters,
- MapQuery,
- VectorSourceRequestMeta,
- VectorSourceSyncMeta,
-} from '../../../../common/descriptor_types';
-import { VECTOR_SHAPE_TYPE } from '../../../../common/constants';
-import { ITooltipProperty } from '../../tooltips/tooltip_property';
-import { DataRequest } from '../../util/data_request';
-
-export interface SourceTooltipConfig {
- tooltipContent: string | null;
- areResultsTrimmed: boolean;
-}
-
-export type GeoJsonFetchMeta = ESSearchSourceResponseMeta;
-
-export type GeoJsonWithMeta = {
- data: FeatureCollection;
- meta?: GeoJsonFetchMeta;
-};
-
-export type BoundsFilters = {
- applyGlobalQuery: boolean;
- filters: Filter[];
- query?: MapQuery;
- sourceQuery?: MapQuery;
- timeFilters: TimeRange;
-};
-
-export interface IVectorSource extends ISource {
- getTooltipProperties(properties: GeoJsonProperties): Promise;
- getBoundsForFilters(
- boundsFilters: BoundsFilters,
- registerCancelCallback: (callback: () => void) => void
- ): MapExtent | null;
- getGeoJsonWithMeta(
- layerName: string,
- searchFilters: MapFilters,
- registerCancelCallback: (callback: () => void) => void,
- isRequestStillActive: () => boolean
- ): Promise;
-
- getFields(): Promise;
- getFieldByName(fieldName: string): IField | null;
- getLeftJoinFields(): Promise;
- getSyncMeta(): VectorSourceSyncMeta;
- getFieldNames(): string[];
- getApplyGlobalQuery(): boolean;
- createField({ fieldName }: { fieldName: string }): IField;
- canFormatFeatureProperties(): boolean;
- getSupportedShapeTypes(): Promise;
- isBoundsAware(): boolean;
- getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig;
-}
-
-export class AbstractVectorSource extends AbstractSource implements IVectorSource {
- getTooltipProperties(properties: GeoJsonProperties): Promise;
- getBoundsForFilters(
- boundsFilters: BoundsFilters,
- registerCancelCallback: (callback: () => void) => void
- ): MapExtent | null;
- getGeoJsonWithMeta(
- layerName: string,
- searchFilters: VectorSourceRequestMeta,
- registerCancelCallback: (callback: () => void) => void,
- isRequestStillActive: () => boolean
- ): Promise;
-
- getFields(): Promise;
- getFieldByName(fieldName: string): IField | null;
- getLeftJoinFields(): Promise;
- getSyncMeta(): VectorSourceSyncMeta;
- getSupportedShapeTypes(): Promise;
- canFormatFeatureProperties(): boolean;
- getApplyGlobalQuery(): boolean;
- getFieldNames(): string[];
- createField({ fieldName }: { fieldName: string }): IField;
- isBoundsAware(): boolean;
- getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig;
-}
-
-export interface ITiledSingleLayerVectorSource extends IVectorSource {
- getUrlTemplateWithMeta(
- searchFilters: VectorSourceRequestMeta
- ): Promise<{
- layerName: string;
- urlTemplate: string;
- minSourceZoom: number;
- maxSourceZoom: number;
- }>;
- getMinZoom(): number;
- getMaxZoom(): number;
- getLayerName(): string;
-}
diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.js b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.js
deleted file mode 100644
index 9569b8626aabf..0000000000000
--- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.js
+++ /dev/null
@@ -1,140 +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 { TooltipProperty } from '../../tooltips/tooltip_property';
-import { AbstractSource } from './../source';
-import * as topojson from 'topojson-client';
-import _ from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { VECTOR_SHAPE_TYPE } from '../../../../common/constants';
-
-export class AbstractVectorSource extends AbstractSource {
- static async getGeoJson({ format, featureCollectionPath, fetchUrl }) {
- let fetchedJson;
- try {
- // TODO proxy map.regionmap url requests through kibana server and then use kfetch
- // Can not use kfetch because fetchUrl may point to external URL. (map.regionmap)
- const response = await fetch(fetchUrl);
- if (!response.ok) {
- throw new Error('Request failed');
- }
- fetchedJson = await response.json();
- } catch (e) {
- throw new Error(
- i18n.translate('xpack.maps.source.vetorSource.requestFailedErrorMessage', {
- defaultMessage: `Unable to fetch vector shapes from url: {fetchUrl}`,
- values: { fetchUrl },
- })
- );
- }
-
- if (format === 'geojson') {
- return fetchedJson;
- }
-
- if (format === 'topojson') {
- const features = _.get(fetchedJson, `objects.${featureCollectionPath}`);
- return topojson.feature(fetchedJson, features);
- }
-
- throw new Error(
- i18n.translate('xpack.maps.source.vetorSource.formatErrorMessage', {
- defaultMessage: `Unable to fetch vector shapes from url: {format}`,
- values: { format },
- })
- );
- }
-
- /**
- * factory function creating a new field-instance
- * @param fieldName
- * @param label
- * @returns {IField}
- */
- createField() {
- throw new Error(`Should implemement ${this.constructor.type} ${this}`);
- }
-
- getFieldNames() {
- return [];
- }
-
- /**
- * Retrieves a field. This may be an existing instance.
- * @param fieldName
- * @param label
- * @returns {IField}
- */
- getFieldByName(name) {
- return this.createField({ fieldName: name });
- }
-
- _getTooltipPropertyNames() {
- return this._tooltipFields.map((field) => field.getName());
- }
-
- isFilterByMapBounds() {
- return false;
- }
-
- isBoundsAware() {
- return false;
- }
-
- async getBoundsForFilters() {
- console.warn('Should implement AbstractVectorSource#getBoundsForFilters');
- return null;
- }
-
- async getFields() {
- return [];
- }
-
- async getLeftJoinFields() {
- return [];
- }
-
- async getGeoJsonWithMeta() {
- throw new Error('Should implement VectorSource#getGeoJson');
- }
-
- canFormatFeatureProperties() {
- return false;
- }
-
- // Allow source to filter and format feature properties before displaying to user
- async getTooltipProperties(properties) {
- const tooltipProperties = [];
- for (const key in properties) {
- if (key.startsWith('__kbn')) {
- //these are system properties and should be ignored
- continue;
- }
- tooltipProperties.push(new TooltipProperty(key, key, properties[key]));
- }
- return tooltipProperties;
- }
-
- async isTimeAware() {
- return false;
- }
-
- showJoinEditor() {
- return true;
- }
-
- async getSupportedShapeTypes() {
- return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
- }
-
- getSourceTooltipContent(/* sourceDataRequest */) {
- return { tooltipContent: null, areResultsTrimmed: false };
- }
-
- getSyncMeta() {
- return {};
- }
-}
diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx
new file mode 100644
index 0000000000000..38ff3b49a87f4
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+// @ts-expect-error
+import * as topojson from 'topojson-client';
+import _ from 'lodash';
+import { i18n } from '@kbn/i18n';
+import { FeatureCollection, GeoJsonProperties } from 'geojson';
+import { Filter, TimeRange } from 'src/plugins/data/public';
+import { FORMAT_TYPE, VECTOR_SHAPE_TYPE } from '../../../../common/constants';
+import { TooltipProperty, ITooltipProperty } from '../../tooltips/tooltip_property';
+import { AbstractSource, ISource } from '../source';
+import { IField } from '../../fields/field';
+import {
+ ESSearchSourceResponseMeta,
+ MapExtent,
+ MapQuery,
+ VectorSourceRequestMeta,
+ VectorSourceSyncMeta,
+} from '../../../../common/descriptor_types';
+import { DataRequest } from '../../util/data_request';
+
+export interface SourceTooltipConfig {
+ tooltipContent: string | null;
+ areResultsTrimmed: boolean;
+}
+
+export type GeoJsonFetchMeta = ESSearchSourceResponseMeta;
+
+export interface GeoJsonWithMeta {
+ data: FeatureCollection;
+ meta?: GeoJsonFetchMeta;
+}
+
+export interface BoundsFilters {
+ applyGlobalQuery: boolean;
+ filters: Filter[];
+ query?: MapQuery;
+ sourceQuery?: MapQuery;
+ timeFilters: TimeRange;
+}
+
+export interface IVectorSource extends ISource {
+ getTooltipProperties(properties: GeoJsonProperties): Promise;
+ getBoundsForFilters(
+ boundsFilters: BoundsFilters,
+ registerCancelCallback: (callback: () => void) => void
+ ): Promise;
+ getGeoJsonWithMeta(
+ layerName: string,
+ searchFilters: VectorSourceRequestMeta,
+ registerCancelCallback: (callback: () => void) => void,
+ isRequestStillActive: () => boolean
+ ): Promise;
+
+ getFields(): Promise;
+ getFieldByName(fieldName: string): IField | null;
+ getLeftJoinFields(): Promise;
+ getSyncMeta(): VectorSourceSyncMeta | null;
+ getFieldNames(): string[];
+ getApplyGlobalQuery(): boolean;
+ createField({ fieldName }: { fieldName: string }): IField;
+ canFormatFeatureProperties(): boolean;
+ getSupportedShapeTypes(): Promise;
+ isBoundsAware(): boolean;
+ getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig;
+}
+
+export interface ITiledSingleLayerVectorSource extends IVectorSource {
+ getUrlTemplateWithMeta(
+ searchFilters: VectorSourceRequestMeta
+ ): Promise<{
+ layerName: string;
+ urlTemplate: string;
+ minSourceZoom: number;
+ maxSourceZoom: number;
+ }>;
+ getMinZoom(): number;
+ getMaxZoom(): number;
+ getLayerName(): string;
+}
+
+export class AbstractVectorSource extends AbstractSource implements IVectorSource {
+ static async getGeoJson({
+ format,
+ featureCollectionPath,
+ fetchUrl,
+ }: {
+ format: FORMAT_TYPE;
+ featureCollectionPath: string;
+ fetchUrl: string;
+ }) {
+ let fetchedJson;
+ try {
+ const response = await fetch(fetchUrl);
+ if (!response.ok) {
+ throw new Error('Request failed');
+ }
+ fetchedJson = await response.json();
+ } catch (e) {
+ throw new Error(
+ i18n.translate('xpack.maps.source.vetorSource.requestFailedErrorMessage', {
+ defaultMessage: `Unable to fetch vector shapes from url: {fetchUrl}`,
+ values: { fetchUrl },
+ })
+ );
+ }
+
+ if (format === FORMAT_TYPE.GEOJSON) {
+ return fetchedJson;
+ }
+
+ if (format === FORMAT_TYPE.TOPOJSON) {
+ const features = _.get(fetchedJson, `objects.${featureCollectionPath}`);
+ return topojson.feature(fetchedJson, features);
+ }
+
+ throw new Error(
+ i18n.translate('xpack.maps.source.vetorSource.formatErrorMessage', {
+ defaultMessage: `Unable to fetch vector shapes from url: {format}`,
+ values: { format },
+ })
+ );
+ }
+
+ getFieldNames(): string[] {
+ return [];
+ }
+
+ createField({ fieldName }: { fieldName: string }): IField {
+ throw new Error('Not implemented');
+ }
+
+ getFieldByName(fieldName: string): IField | null {
+ return this.createField({ fieldName });
+ }
+
+ isFilterByMapBounds() {
+ return false;
+ }
+
+ isBoundsAware(): boolean {
+ return false;
+ }
+
+ async getBoundsForFilters(
+ boundsFilters: BoundsFilters,
+ registerCancelCallback: (callback: () => void) => void
+ ): Promise {
+ return null;
+ }
+
+ async getFields(): Promise {
+ return [];
+ }
+
+ async getLeftJoinFields(): Promise {
+ return [];
+ }
+
+ async getGeoJsonWithMeta(
+ layerName: string,
+ searchFilters: VectorSourceRequestMeta,
+ registerCancelCallback: (callback: () => void) => void,
+ isRequestStillActive: () => boolean
+ ): Promise {
+ throw new Error('Should implement VectorSource#getGeoJson');
+ }
+
+ canFormatFeatureProperties() {
+ return false;
+ }
+
+ // Allow source to filter and format feature properties before displaying to user
+ async getTooltipProperties(properties: GeoJsonProperties): Promise {
+ const tooltipProperties: ITooltipProperty[] = [];
+ for (const key in properties) {
+ if (key.startsWith('__kbn')) {
+ // these are system properties and should be ignored
+ continue;
+ }
+ tooltipProperties.push(new TooltipProperty(key, key, properties[key]));
+ }
+ return tooltipProperties;
+ }
+
+ async isTimeAware() {
+ return false;
+ }
+
+ showJoinEditor() {
+ return true;
+ }
+
+ async getSupportedShapeTypes() {
+ return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
+ }
+
+ getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig {
+ return { tooltipContent: null, areResultsTrimmed: false };
+ }
+
+ getSyncMeta(): VectorSourceSyncMeta | null {
+ return null;
+ }
+}
diff --git a/x-pack/plugins/maps/public/classes/util/valid_string_config.ts b/x-pack/plugins/maps/public/classes/util/valid_string_config.ts
new file mode 100644
index 0000000000000..29080f7988bea
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/util/valid_string_config.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/**
+ * Validate user-generated data (e.g. descriptors). Possibly dirty or of wrong type.
+ * @param value
+ */
+export function isValidStringConfig(value: any): boolean {
+ return typeof value === 'string' && value !== '';
+}
diff --git a/x-pack/plugins/maps/public/connected_components/_index.scss b/x-pack/plugins/maps/public/connected_components/_index.scss
index a952b3b545922..19c11d3fde662 100644
--- a/x-pack/plugins/maps/public/connected_components/_index.scss
+++ b/x-pack/plugins/maps/public/connected_components/_index.scss
@@ -2,4 +2,4 @@
@import 'layer_panel/index';
@import 'widget_overlay/index';
@import 'toolbar_overlay/index';
-@import 'map/features_tooltip/index';
+@import 'mb_map/features_tooltip/index';
diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
index 352aed4a8cc93..169875e63a536 100644
--- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
+++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
@@ -13,7 +13,7 @@ import uuid from 'uuid/v4';
import { Filter } from 'src/plugins/data/public';
import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public';
// @ts-expect-error
-import { MBMap } from '../map/mb';
+import { MBMap } from '../mb_map';
// @ts-expect-error
import { WidgetOverlay } from '../widget_overlay';
// @ts-expect-error
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_circle.ts b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_circle.ts
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_circle.ts
rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_circle.ts
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js
similarity index 97%
rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js
index 0356a8267c18a..089d4be28dff7 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js
@@ -6,7 +6,7 @@
import _ from 'lodash';
import React from 'react';
-import { DRAW_TYPE } from '../../../../../common/constants';
+import { DRAW_TYPE } from '../../../../common/constants';
import MapboxDraw from '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw-unminified';
import DrawRectangle from 'mapbox-gl-draw-rectangle-mode';
import { DrawCircle } from './draw_circle';
@@ -15,7 +15,7 @@ import {
createSpatialFilterWithGeometry,
getBoundingBoxGeometry,
roundCoordinates,
-} from '../../../../../common/elasticsearch_util';
+} from '../../../../common/elasticsearch_util';
import { DrawTooltip } from './draw_tooltip';
const DRAW_RECTANGLE = 'draw_rectangle';
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_tooltip.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js
similarity index 97%
rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_tooltip.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js
index c8bde29b94fb6..dd93b038ff8a1 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_tooltip.js
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js
@@ -8,7 +8,7 @@ import _ from 'lodash';
import React, { Component } from 'react';
import { EuiPopover, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { DRAW_TYPE } from '../../../../../common/constants';
+import { DRAW_TYPE } from '../../../../common/constants';
const noop = () => {};
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/index.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js
similarity index 83%
rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/index.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js
index bc026c41fcf0a..230ad5b3f39d5 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/index.js
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js
@@ -6,8 +6,8 @@
import { connect } from 'react-redux';
import { DrawControl } from './draw_control';
-import { updateDrawState } from '../../../../actions';
-import { getDrawState, isDrawingFilter } from '../../../../selectors/map_selectors';
+import { updateDrawState } from '../../../actions';
+import { getDrawState, isDrawingFilter } from '../../../selectors/map_selectors';
function mapStateToProps(state = {}) {
return {
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/feature_properties.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/feature_properties.test.js.snap
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/feature_properties.test.js.snap
rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/feature_properties.test.js.snap
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/tooltip_header.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/tooltip_header.test.js.snap
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/tooltip_header.test.js.snap
rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/tooltip_header.test.js.snap
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/_index.scss b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/_index.scss
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/_index.scss
rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/_index.scss
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.test.js
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.test.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.test.js
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.js
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.js
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.js
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.js
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.test.js
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.test.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.test.js
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts b/x-pack/plugins/maps/public/connected_components/mb_map/get_initial_view.ts
similarity index 87%
rename from x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts
rename to x-pack/plugins/maps/public/connected_components/mb_map/get_initial_view.ts
index 20fb8186f9870..853819eb289a3 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/get_initial_view.ts
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { INITIAL_LOCATION } from '../../../../common/constants';
-import { Goto, MapCenterAndZoom } from '../../../../common/descriptor_types';
-import { MapSettings } from '../../../reducers/map';
+import { INITIAL_LOCATION } from '../../../common/constants';
+import { Goto, MapCenterAndZoom } from '../../../common/descriptor_types';
+import { MapSettings } from '../../reducers/map';
export async function getInitialView(
goto: Goto | null,
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/image_utils.js b/x-pack/plugins/maps/public/connected_components/mb_map/image_utils.js
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/mb/image_utils.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/image_utils.js
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/index.js b/x-pack/plugins/maps/public/connected_components/mb_map/index.js
similarity index 84%
rename from x-pack/plugins/maps/public/connected_components/map/mb/index.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/index.js
index 4b8df07bd1f39..cccd5e571d3e8 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/index.js
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/index.js
@@ -5,7 +5,7 @@
*/
import { connect } from 'react-redux';
-import { MBMap } from './view';
+import { MBMap } from './mb_map';
import {
mapExtentChanged,
mapReady,
@@ -14,7 +14,7 @@ import {
clearMouseCoordinates,
clearGoto,
setMapInitError,
-} from '../../../actions';
+} from '../../actions';
import {
getLayerList,
getMapReady,
@@ -25,9 +25,9 @@ import {
isViewControlHidden,
getSpatialFiltersLayer,
getMapSettings,
-} from '../../../selectors/map_selectors';
+} from '../../selectors/map_selectors';
-import { getInspectorAdapters } from '../../../reducers/non_serializable_instances';
+import { getInspectorAdapters } from '../../reducers/non_serializable_instances';
function mapStateToProps(state = {}) {
return {
@@ -72,7 +72,5 @@ function mapDispatchToProps(dispatch) {
};
}
-const connectedMBMap = connect(mapStateToProps, mapDispatchToProps, null, {
- forwardRef: true,
-})(MBMap);
-export { connectedMBMap as MBMap };
+const connected = connect(mapStateToProps, mapDispatchToProps)(MBMap);
+export { connected as MBMap };
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/mb.utils.test.js
similarity index 98%
rename from x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/mb.utils.test.js
index e2050724ef684..a28cc75f6d89d 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb.utils.test.js
@@ -5,7 +5,7 @@
*/
import { removeOrphanedSourcesAndLayers } from './utils';
-import { SPATIAL_FILTERS_LAYER_ID } from '../../../../common/constants';
+import { SPATIAL_FILTERS_LAYER_ID } from '../../../common/constants';
import _ from 'lodash';
class MockMbMap {
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js
similarity index 97%
rename from x-pack/plugins/maps/public/connected_components/map/mb/view.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js
index ddc48cfc9c329..04c376a093623 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js
@@ -6,15 +6,15 @@
import _ from 'lodash';
import React from 'react';
-import { ResizeChecker } from '../../../../../../../src/plugins/kibana_utils/public';
+import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public';
import { removeOrphanedSourcesAndLayers, addSpritesheetToMap } from './utils';
import { syncLayerOrder } from './sort_layers';
-import { getGlyphUrl, isRetina } from '../../../meta';
+import { getGlyphUrl, isRetina } from '../../meta';
import {
DECIMAL_DEGREES_PRECISION,
KBN_TOO_MANY_FEATURES_IMAGE_ID,
ZOOM_PRECISION,
-} from '../../../../common/constants';
+} from '../../../common/constants';
import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp';
import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker';
import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js';
@@ -23,9 +23,9 @@ import sprites1 from '@elastic/maki/dist/sprite@1.png';
import sprites2 from '@elastic/maki/dist/sprite@2.png';
import { DrawControl } from './draw_control';
import { TooltipControl } from './tooltip_control';
-import { clampToLatBounds, clampToLonBounds } from '../../../../common/elasticsearch_util';
+import { clampToLatBounds, clampToLonBounds } from '../../../common/elasticsearch_util';
import { getInitialView } from './get_initial_view';
-import { getPreserveDrawingBuffer } from '../../../kibana_services';
+import { getPreserveDrawingBuffer } from '../../kibana_services';
mapboxgl.workerUrl = mbWorkerUrl;
mapboxgl.setRTLTextPlugin(mbRtlPlugin);
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts
similarity index 98%
rename from x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts
rename to x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts
index e26a1e43509c8..9e85c7b04b266 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts
@@ -8,8 +8,8 @@
import _ from 'lodash';
import { Map as MbMap, Layer as MbLayer, Style as MbStyle } from 'mapbox-gl';
import { getIsTextLayer, syncLayerOrder } from './sort_layers';
-import { SPATIAL_FILTERS_LAYER_ID } from '../../../../common/constants';
-import { ILayer } from '../../../classes/layers/layer';
+import { SPATIAL_FILTERS_LAYER_ID } from '../../../common/constants';
+import { ILayer } from '../../classes/layers/layer';
let moveCounter = 0;
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts
similarity index 98%
rename from x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts
rename to x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts
index 0c970fe663557..dda43269e32d8 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts
@@ -5,7 +5,7 @@
*/
import { Map as MbMap, Layer as MbLayer } from 'mapbox-gl';
-import { ILayer } from '../../../classes/layers/layer';
+import { ILayer } from '../../classes/layers/layer';
// "Layer" is overloaded and can mean the following
// 1) Map layer (ILayer): A single map layer consists of one to many mapbox layers.
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_control.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_control.test.js.snap
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_control.test.js.snap
rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_control.test.js.snap
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_popover.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_popover.test.js.snap
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_popover.test.js.snap
rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_popover.test.js.snap
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/index.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.js
similarity index 94%
rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/index.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.js
index 407dcf1997aeb..7d2f2b05d6f11 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/index.js
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.js
@@ -11,13 +11,13 @@ import {
openOnClickTooltip,
closeOnHoverTooltip,
openOnHoverTooltip,
-} from '../../../../actions';
+} from '../../../actions';
import {
getLayerList,
getOpenTooltips,
getHasLockedTooltips,
isDrawingFilter,
-} from '../../../../selectors/map_selectors';
+} from '../../../selectors/map_selectors';
function mapStateToProps(state = {}) {
return {
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js
similarity index 98%
rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js
index edfeb3c76b104..b178eef6fa5d3 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js
@@ -6,9 +6,9 @@
import _ from 'lodash';
import React from 'react';
-import { FEATURE_ID_PROPERTY_NAME, LON_INDEX } from '../../../../../common/constants';
+import { FEATURE_ID_PROPERTY_NAME, LON_INDEX } from '../../../../common/constants';
import { TooltipPopover } from './tooltip_popover';
-import { EXCLUDE_TOO_MANY_FEATURES_BOX } from '../../../../classes/util/mb_filter_expressions';
+import { EXCLUDE_TOO_MANY_FEATURES_BOX } from '../../../classes/util/mb_filter_expressions';
function justifyAnchorLocation(mbLngLat, targetFeature) {
let popupAnchorLocation = [mbLngLat.lng, mbLngLat.lat]; // default popup location to mouse location
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.js
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.test.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.js
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js
similarity index 97%
rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js
index 4cfddf0034039..ca4864f79940e 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js
@@ -5,8 +5,8 @@
*/
import React, { Component } from 'react';
-import { LAT_INDEX, LON_INDEX } from '../../../../../common/constants';
-import { FeaturesTooltip } from '../../features_tooltip/features_tooltip';
+import { LAT_INDEX, LON_INDEX } from '../../../../common/constants';
+import { FeaturesTooltip } from '../features_tooltip/features_tooltip';
import { EuiPopover, EuiText } from '@elastic/eui';
const noop = () => {};
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.js
similarity index 98%
rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.js
index 205ca7337277d..b15c3fce6c0b7 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.js
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-jest.mock('../../features_tooltip/features_tooltip', () => ({
+jest.mock('../features_tooltip/features_tooltip', () => ({
FeaturesTooltip: () => {
return mockFeaturesTooltip
;
},
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/utils.js b/x-pack/plugins/maps/public/connected_components/mb_map/utils.js
similarity index 100%
rename from x-pack/plugins/maps/public/connected_components/map/mb/utils.js
rename to x-pack/plugins/maps/public/connected_components/mb_map/utils.js
diff --git a/x-pack/plugins/maps/public/maps_vis_type_alias.js b/x-pack/plugins/maps/public/maps_vis_type_alias.js
index b7e95cdf987db..a2e76216c7def 100644
--- a/x-pack/plugins/maps/public/maps_vis_type_alias.js
+++ b/x-pack/plugins/maps/public/maps_vis_type_alias.js
@@ -16,14 +16,6 @@ export function getMapsVisTypeAlias(visualizations, showMapVisualizationTypes) {
defaultMessage: 'Create and style maps with multiple layers and indices.',
});
- const legacyMapVisualizationWarning = i18n.translate(
- 'xpack.maps.visTypeAlias.legacyMapVizWarning',
- {
- defaultMessage: `Use the Maps app instead of Coordinate Map and Region Map.
-The Maps app offers more functionality and is easier to use.`,
- }
- );
-
return {
aliasApp: APP_ID,
aliasPath: `/${MAP_PATH}`,
@@ -31,9 +23,7 @@ The Maps app offers more functionality and is easier to use.`,
title: i18n.translate('xpack.maps.visTypeAlias.title', {
defaultMessage: 'Maps',
}),
- description: showMapVisualizationTypes
- ? `${description} ${legacyMapVisualizationWarning}`
- : description,
+ description: description,
icon: APP_ICON,
stage: 'production',
};
diff --git a/x-pack/plugins/maps/public/meta.ts b/x-pack/plugins/maps/public/meta.ts
index 4eca6c3e671b7..929050338de72 100644
--- a/x-pack/plugins/maps/public/meta.ts
+++ b/x-pack/plugins/maps/public/meta.ts
@@ -29,8 +29,9 @@ import {
getKibanaVersion,
} from './kibana_services';
import { getLicenseId } from './licensed_features';
+import { LayerConfig } from '../../../../src/plugins/region_map/config';
-export function getKibanaRegionList(): unknown[] {
+export function getKibanaRegionList(): LayerConfig[] {
return getRegionmapLayers();
}
diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts
index 0b797c7b8ef60..75a3f8ef5ede8 100644
--- a/x-pack/plugins/maps/public/plugin.ts
+++ b/x-pack/plugins/maps/public/plugin.ts
@@ -28,8 +28,12 @@ import { featureCatalogueEntry } from './feature_catalogue_entry';
// @ts-ignore
import { getMapsVisTypeAlias } from './maps_vis_type_alias';
import { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
-import { VisualizationsSetup } from '../../../../src/plugins/visualizations/public';
+import {
+ VisualizationsSetup,
+ VisualizationsStart,
+} from '../../../../src/plugins/visualizations/public';
import { APP_ICON_SOLUTION, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants';
+import { PLUGIN_ID_OSS } from '../../../../src/plugins/maps_oss/common/constants';
import { VISUALIZE_GEO_FIELD_TRIGGER } from '../../../../src/plugins/ui_actions/public';
import {
createMapsUrlGenerator,
@@ -72,6 +76,7 @@ export interface MapsPluginStartDependencies {
navigation: NavigationPublicPluginStart;
uiActions: UiActionsStart;
share: SharePluginStart;
+ visualizations: VisualizationsStart;
savedObjects: SavedObjectsStart;
}
@@ -145,6 +150,8 @@ export class MapsPlugin
setLicensingPluginStart(plugins.licensing);
plugins.uiActions.addTriggerAction(VISUALIZE_GEO_FIELD_TRIGGER, visualizeGeoFieldAction);
setStartServices(core, plugins);
+ // unregisters the OSS alias
+ plugins.visualizations.unRegisterAlias(PLUGIN_ID_OSS);
return {
createSecurityLayerDescriptors,
diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts
index 4b5122050eb71..eac71e627fd7d 100644
--- a/x-pack/plugins/maps/public/selectors/map_selectors.ts
+++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts
@@ -34,6 +34,7 @@ import {
import { extractFeaturesFromFilters } from '../../common/elasticsearch_util';
import { MapStoreState } from '../reducers/store';
import {
+ AbstractSourceDescriptor,
DataRequestDescriptor,
DrawState,
Goto,
@@ -94,7 +95,13 @@ export function createLayerInstance(
}
}
-function createSourceInstance(sourceDescriptor: any, inspectorAdapters?: Adapters): ISource {
+function createSourceInstance(
+ sourceDescriptor: AbstractSourceDescriptor | null,
+ inspectorAdapters?: Adapters
+): ISource {
+ if (sourceDescriptor === null) {
+ throw new Error('Source-descriptor should be initialized');
+ }
const source = getSourceByType(sourceDescriptor.type);
if (!source) {
throw new Error(`Unrecognized sourceType ${sourceDescriptor.type}`);
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts
index 785f3ac9cd4dc..d46adff3de2a3 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts
@@ -33,6 +33,7 @@ export const MAX_COLUMNS = 10;
export const DEFAULT_REGRESSION_COLUMNS = 8;
export const BASIC_NUMERICAL_TYPES = new Set([
+ ES_FIELD_TYPES.UNSIGNED_LONG,
ES_FIELD_TYPES.LONG,
ES_FIELD_TYPES.INTEGER,
ES_FIELD_TYPES.SHORT,
diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
index c3b1de64c3eb5..fec60f221b4fc 100644
--- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
@@ -27,6 +27,7 @@ const supportedTypes: string[] = [
ES_FIELD_TYPES.INTEGER,
ES_FIELD_TYPES.FLOAT,
ES_FIELD_TYPES.LONG,
+ ES_FIELD_TYPES.UNSIGNED_LONG,
ES_FIELD_TYPES.BYTE,
ES_FIELD_TYPES.HALF_FLOAT,
ES_FIELD_TYPES.SCALED_FLOAT,
@@ -245,6 +246,7 @@ function getNumericalFields(fields: Field[]): Field[] {
return fields.filter(
(f) =>
f.type === ES_FIELD_TYPES.LONG ||
+ f.type === ES_FIELD_TYPES.UNSIGNED_LONG ||
f.type === ES_FIELD_TYPES.INTEGER ||
f.type === ES_FIELD_TYPES.SHORT ||
f.type === ES_FIELD_TYPES.BYTE ||
diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts
index 5aa7dd326af50..6e203ae18f30f 100644
--- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts
+++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts
@@ -82,6 +82,10 @@ export const analysisConfigSchema = schema.object({
detectors: schema.arrayOf(detectorSchema),
influencers: schema.arrayOf(schema.maybe(schema.string())),
categorization_field_name: schema.maybe(schema.string()),
+ categorization_analyzer: schema.maybe(schema.any()),
+ categorization_filters: schema.maybe(schema.arrayOf(schema.string())),
+ latency: schema.maybe(schema.number()),
+ multivariate_by_fields: schema.maybe(schema.boolean()),
per_partition_categorization: schema.maybe(
schema.object({
enabled: schema.boolean(),
diff --git a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx
index 11ba8214ff81e..5054c47245f0f 100644
--- a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx
@@ -16,6 +16,9 @@ export function createCpuUsageAlertType(): AlertTypeModel {
name: ALERT_DETAILS[ALERT_CPU_USAGE].label,
description: ALERT_DETAILS[ALERT_CPU_USAGE].description,
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-cpu-threshold`;
+ },
alertParamsExpression: (props: Props) => (
),
diff --git a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx
index 7c44e37904ec5..00b70658e4289 100644
--- a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx
@@ -18,6 +18,9 @@ export function createDiskUsageAlertType(): AlertTypeModel {
name: ALERT_DETAILS[ALERT_DISK_USAGE].label,
description: ALERT_DETAILS[ALERT_DISK_USAGE].description,
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-disk-usage-threshold`;
+ },
alertParamsExpression: (props: Props) => (
),
diff --git a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx
index ca7af2fe64e78..c8d0a7a5d49f2 100644
--- a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx
@@ -18,6 +18,9 @@ export function createLegacyAlertTypes(): AlertTypeModel[] {
name: LEGACY_ALERT_DETAILS[legacyAlert].label,
description: LEGACY_ALERT_DETAILS[legacyAlert].description,
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/cluster-alerts.html`;
+ },
alertParamsExpression: () => (
diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx
index 14fb7147179c1..062c32c758794 100644
--- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx
@@ -18,6 +18,9 @@ export function createMemoryUsageAlertType(): AlertTypeModel {
name: ALERT_DETAILS[ALERT_MEMORY_USAGE].label,
description: ALERT_DETAILS[ALERT_MEMORY_USAGE].description,
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-jvm-memory-threshold`;
+ },
alertParamsExpression: (props: Props) => (
),
diff --git a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx
index 4c8f00f8385c2..ec97a45a8a800 100644
--- a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx
@@ -16,6 +16,9 @@ export function createMissingMonitoringDataAlertType(): AlertTypeModel {
name: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].label,
description: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].description,
iconClass: 'bell',
+ documentationUrl(docLinks) {
+ return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-missing-monitoring-data`;
+ },
alertParamsExpression: (props: any) => (
(
<>
diff --git a/x-pack/plugins/observability/common/annotations.ts b/x-pack/plugins/observability/common/annotations.ts
index 6aea4d3d92f9b..f7ab243cf73f3 100644
--- a/x-pack/plugins/observability/common/annotations.ts
+++ b/x-pack/plugins/observability/common/annotations.ts
@@ -5,7 +5,24 @@
*/
import * as t from 'io-ts';
-import { dateAsStringRt } from '../../apm/common/runtime_types/date_as_string_rt';
+import { either } from 'fp-ts/lib/Either';
+
+/**
+ * Checks whether a string is a valid ISO timestamp,
+ * but doesn't convert it into a Date object when decoding.
+ *
+ * Copied from x-pack/plugins/apm/common/runtime_types/date_as_string_rt.ts.
+ */
+const dateAsStringRt = new t.Type(
+ 'DateAsString',
+ t.string.is,
+ (input, context) =>
+ either.chain(t.string.validate(input, context), (str) => {
+ const date = new Date(str);
+ return isNaN(date.getTime()) ? t.failure(input, context) : t.success(str);
+ }),
+ t.identity
+);
export const createAnnotationRt = t.intersection([
t.type({
diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.js
index c8badc92ab11c..f420e83adc031 100644
--- a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.js
+++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.js
@@ -3,10 +3,10 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import { act } from 'react-dom/test-utils';
import { registerTestBed } from '../../../../../test_utils';
-/* eslint-disable @kbn/eslint/no-restricted-paths */
import { RemoteClusterAdd } from '../../../public/application/sections/remote_cluster_add';
import { createRemoteClustersStore } from '../../../public/application/store';
import { registerRouter } from '../../../public/application/services/routing';
@@ -24,8 +24,12 @@ export const setup = (props) => {
const testBed = initTestBed(props);
// User actions
- const clickSaveForm = () => {
- testBed.find('remoteClusterFormSaveButton').simulate('click');
+ const clickSaveForm = async () => {
+ await act(async () => {
+ testBed.find('remoteClusterFormSaveButton').simulate('click');
+ });
+
+ testBed.component.update();
};
return {
diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.js
index 05a4a2e330325..545e3dd0ba969 100644
--- a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.js
+++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.js
@@ -3,8 +3,9 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import { act } from 'react-dom/test-utils';
-import { nextTick, setupEnvironment } from '../helpers';
+import { setupEnvironment } from '../helpers';
import { NON_ALPHA_NUMERIC_CHARS, ACCENTED_CHARS } from './special_characters';
import { setup } from './remote_clusters_add.helpers';
@@ -15,6 +16,7 @@ describe('Create Remote cluster', () => {
let actions;
let form;
let server;
+ let component;
beforeAll(() => {
({ server } = setupEnvironment());
@@ -24,8 +26,11 @@ describe('Create Remote cluster', () => {
server.restore();
});
- beforeEach(() => {
- ({ form, exists, find, actions } = setup());
+ beforeEach(async () => {
+ await act(async () => {
+ ({ form, exists, find, actions, component } = setup());
+ });
+ component.update();
});
test('should have the title of the page set correctly', () => {
@@ -45,7 +50,11 @@ describe('Create Remote cluster', () => {
false
);
- form.toggleEuiSwitch('remoteClusterFormSkipUnavailableFormToggle');
+ act(() => {
+ form.toggleEuiSwitch('remoteClusterFormSkipUnavailableFormToggle');
+ });
+
+ component.update();
expect(find('remoteClusterFormSkipUnavailableFormToggle').props()['aria-checked']).toBe(true);
});
@@ -56,16 +65,20 @@ describe('Create Remote cluster', () => {
// By default it should be set to "false"
expect(find('remoteClusterFormConnectionModeToggle').props()['aria-checked']).toBe(false);
- form.toggleEuiSwitch('remoteClusterFormConnectionModeToggle');
+ act(() => {
+ form.toggleEuiSwitch('remoteClusterFormConnectionModeToggle');
+ });
+
+ component.update();
expect(find('remoteClusterFormConnectionModeToggle').props()['aria-checked']).toBe(true);
});
- test('should display errors and disable the save button when clicking "save" without filling the form', () => {
+ test('should display errors and disable the save button when clicking "save" without filling the form', async () => {
expect(exists('remoteClusterFormGlobalError')).toBe(false);
expect(find('remoteClusterFormSaveButton').props().disabled).toBe(false);
- actions.clickSaveForm();
+ await actions.clickSaveForm();
expect(exists('remoteClusterFormGlobalError')).toBe(true);
expect(form.getErrorsMessages()).toEqual([
@@ -83,19 +96,22 @@ describe('Create Remote cluster', () => {
let form;
beforeEach(async () => {
- ({ component, form, actions } = setup());
+ await act(async () => {
+ ({ component, form, actions } = setup());
+ });
- await nextTick();
component.update();
});
- test('should not allow spaces', () => {
+ test('should not allow spaces', async () => {
form.setInputValue('remoteClusterFormNameInput', 'with space');
- actions.clickSaveForm();
+
+ await actions.clickSaveForm();
+
expect(form.getErrorsMessages()).toContain('Spaces are not allowed in the name.');
});
- test('should only allow alpha-numeric characters, "-" (dash) and "_" (underscore)', () => {
+ test('should only allow alpha-numeric characters, "-" (dash) and "_" (underscore)', async () => {
const expectInvalidChar = (char) => {
if (char === '-' || char === '_') {
return;
@@ -103,6 +119,7 @@ describe('Create Remote cluster', () => {
try {
form.setInputValue('remoteClusterFormNameInput', `with${char}`);
+
expect(form.getErrorsMessages()).toContain(
`Remove the character ${char} from the name.`
);
@@ -111,7 +128,7 @@ describe('Create Remote cluster', () => {
}
};
- actions.clickSaveForm(); // display form errors
+ await actions.clickSaveForm(); // display form errors
[...NON_ALPHA_NUMERIC_CHARS, ...ACCENTED_CHARS].forEach(expectInvalidChar);
});
@@ -120,13 +137,20 @@ describe('Create Remote cluster', () => {
describe('seeds', () => {
let actions;
let form;
+ let component;
beforeEach(async () => {
- ({ form, actions } = setup());
+ await act(async () => {
+ ({ form, actions, component } = setup());
+ });
+
+ component.update();
+
+ form.setInputValue('remoteClusterFormNameInput', 'remote_cluster_test');
});
- test('should only allow alpha-numeric characters and "-" (dash) in the node "host" part', () => {
- actions.clickSaveForm(); // display form errors
+ test('should only allow alpha-numeric characters and "-" (dash) in the node "host" part', async () => {
+ await actions.clickSaveForm(); // display form errors
const notInArray = (array) => (value) => array.indexOf(value) < 0;
@@ -142,8 +166,8 @@ describe('Create Remote cluster', () => {
.forEach(expectInvalidChar);
});
- test('should require a numeric "port" to be set', () => {
- actions.clickSaveForm();
+ test('should require a numeric "port" to be set', async () => {
+ await actions.clickSaveForm();
form.setComboBoxValue('remoteClusterFormSeedsInput', '192.168.1.1');
expect(form.getErrorsMessages()).toContain('A port is required.');
@@ -156,16 +180,25 @@ describe('Create Remote cluster', () => {
describe('proxy address', () => {
let actions;
let form;
+ let component;
beforeEach(async () => {
- ({ form, actions } = setup());
+ await act(async () => {
+ ({ form, actions, component } = setup());
+ });
- // Enable "proxy" mode
- form.toggleEuiSwitch('remoteClusterFormConnectionModeToggle');
+ component.update();
+
+ act(() => {
+ // Enable "proxy" mode
+ form.toggleEuiSwitch('remoteClusterFormConnectionModeToggle');
+ });
+
+ component.update();
});
- test('should only allow alpha-numeric characters and "-" (dash) in the proxy address "host" part', () => {
- actions.clickSaveForm(); // display form errors
+ test('should only allow alpha-numeric characters and "-" (dash) in the proxy address "host" part', async () => {
+ await actions.clickSaveForm(); // display form errors
const notInArray = (array) => (value) => array.indexOf(value) < 0;
@@ -181,8 +214,8 @@ describe('Create Remote cluster', () => {
.forEach(expectInvalidChar);
});
- test('should require a numeric "port" to be set', () => {
- actions.clickSaveForm();
+ test('should require a numeric "port" to be set', async () => {
+ await actions.clickSaveForm();
form.setInputValue('remoteClusterFormProxyAddressInput', '192.168.1.1');
expect(form.getErrorsMessages()).toContain('A port is required.');
diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.js
index b5402f3b017f0..331ef24d1d8a1 100644
--- a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.js
+++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.js
@@ -6,7 +6,6 @@
import { registerTestBed } from '../../../../../test_utils';
-/* eslint-disable @kbn/eslint/no-restricted-paths */
import { RemoteClusterEdit } from '../../../public/application/sections/remote_cluster_edit';
import { createRemoteClustersStore } from '../../../public/application/store';
import { registerRouter } from '../../../public/application/services/routing';
diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.js
index b0e0832cb0831..d3dee936c68dc 100644
--- a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.js
+++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.js
@@ -16,28 +16,23 @@ import {
} from './remote_clusters_edit.helpers';
describe('Edit Remote cluster', () => {
- let server;
- let httpRequestsMockHelpers;
let component;
let find;
let exists;
- let waitFor;
- beforeAll(() => {
- ({ server, httpRequestsMockHelpers } = setupEnvironment());
- });
+ const { server, httpRequestsMockHelpers } = setupEnvironment();
afterAll(() => {
server.restore();
});
- beforeEach(async () => {
- httpRequestsMockHelpers.setLoadRemoteClustersResponse([REMOTE_CLUSTER_EDIT]);
+ httpRequestsMockHelpers.setLoadRemoteClustersResponse([REMOTE_CLUSTER_EDIT]);
+ beforeEach(async () => {
await act(async () => {
- ({ component, find, exists, waitFor } = setup());
- await waitFor('remoteClusterForm');
+ ({ component, find, exists } = setup());
});
+ component.update();
});
test('should have the title of the page set correctly', () => {
@@ -59,9 +54,10 @@ describe('Edit Remote cluster', () => {
await act(async () => {
addRemoteClusterTestBed = setupRemoteClustersAdd();
- addRemoteClusterTestBed.waitFor('remoteClusterAddPage');
});
+ addRemoteClusterTestBed.component.update();
+
const formEdit = component.find(RemoteClusterForm);
const formAdd = addRemoteClusterTestBed.component.find(RemoteClusterForm);
diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js
index c912a4ddabc9d..de5c1e5290540 100644
--- a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js
+++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js
@@ -3,20 +3,17 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import axios from 'axios';
+import axiosXhrAdapter from 'axios/lib/adapters/xhr';
import {
notificationServiceMock,
fatalErrorsServiceMock,
docLinksServiceMock,
- injectedMetadataServiceMock,
} from '../../../../../../src/core/public/mocks';
import { usageCollectionPluginMock } from '../../../../../../src/plugins/usage_collection/public/mocks';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { HttpService } from '../../../../../../src/core/public/http';
-
-/* eslint-disable @kbn/eslint/no-restricted-paths */
import { init as initBreadcrumb } from '../../../public/application/services/breadcrumb';
import { init as initHttp } from '../../../public/application/services/http';
import { init as initNotification } from '../../../public/application/services/notification';
@@ -25,10 +22,10 @@ import { init as initDocumentation } from '../../../public/application/services/
import { init as initHttpRequests } from './http_requests';
export const setupEnvironment = () => {
- const httpServiceSetupMock = new HttpService().setup({
- injectedMetadata: injectedMetadataServiceMock.createSetupContract(),
- fatalErrors: fatalErrorsServiceMock.createSetupContract(),
- });
+ // axios has a similar interface to HttpSetup, but we
+ // flatten out the response.
+ const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });
+ mockHttpClient.interceptors.response.use(({ data }) => data);
initBreadcrumb(() => {});
initDocumentation(docLinksServiceMock.createStartContract());
@@ -37,7 +34,7 @@ export const setupEnvironment = () => {
notificationServiceMock.createSetupContract().toasts,
fatalErrorsServiceMock.createSetupContract()
);
- initHttp(httpServiceSetupMock);
+ initHttp(mockHttpClient);
const { server, httpRequestsMockHelpers } = initHttpRequests();
diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js
index ce9638d95bd28..5f34728def3d3 100644
--- a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js
+++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js
@@ -3,10 +3,10 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import { act } from 'react-dom/test-utils';
import { registerTestBed, findTestSubject } from '../../../../../test_utils';
-/* eslint-disable @kbn/eslint/no-restricted-paths */
import { RemoteClusterList } from '../../../public/application/sections/remote_cluster_list';
import { createRemoteClustersStore } from '../../../public/application/store';
import { registerRouter } from '../../../public/application/services/routing';
@@ -29,15 +29,26 @@ export const setup = (props) => {
const { rows } = testBed.table.getMetaData(EUI_TABLE);
const row = rows[index];
const checkBox = row.reactWrapper.find('input').hostNodes();
- checkBox.simulate('change', { target: { checked: true } });
+
+ act(() => {
+ checkBox.simulate('change', { target: { checked: true } });
+ });
+
+ testBed.component.update();
};
const clickBulkDeleteButton = () => {
- testBed.find('remoteClusterBulkDeleteButton').simulate('click');
+ const { find, component } = testBed;
+ act(() => {
+ find('remoteClusterBulkDeleteButton').simulate('click');
+ });
+
+ component.update();
};
const clickRowActionButtonAt = (index = 0, action = 'delete') => {
- const { rows } = testBed.table.getMetaData(EUI_TABLE);
+ const { table, component } = testBed;
+ const { rows } = table.getMetaData(EUI_TABLE);
const indexLastColumn = rows[index].columns.length - 1;
const tableCellActions = rows[index].columns[indexLastColumn].reactWrapper;
@@ -45,32 +56,54 @@ export const setup = (props) => {
if (action === 'delete') {
button = findTestSubject(tableCellActions, 'remoteClusterTableRowRemoveButton');
} else if (action === 'edit') {
- findTestSubject(tableCellActions, 'remoteClusterTableRowEditButton');
+ button = findTestSubject(tableCellActions, 'remoteClusterTableRowEditButton');
}
if (!button) {
throw new Error(`Button for action "${action}" not found.`);
}
- button.simulate('click');
+ act(() => {
+ button.simulate('click');
+ });
+
+ component.update();
};
const clickConfirmModalDeleteRemoteCluster = () => {
- const modal = testBed.find('remoteClustersDeleteConfirmModal');
- findTestSubject(modal, 'confirmModalConfirmButton').simulate('click');
+ const { find, component } = testBed;
+ const modal = find('remoteClustersDeleteConfirmModal');
+
+ act(() => {
+ findTestSubject(modal, 'confirmModalConfirmButton').simulate('click');
+ });
+
+ component.update();
};
const clickRemoteClusterAt = (index = 0) => {
- const { rows } = testBed.table.getMetaData(EUI_TABLE);
+ const { table, component } = testBed;
+ const { rows } = table.getMetaData(EUI_TABLE);
const remoteClusterLink = findTestSubject(
rows[index].reactWrapper,
'remoteClustersTableListClusterLink'
);
- remoteClusterLink.simulate('click');
+
+ act(() => {
+ remoteClusterLink.simulate('click');
+ });
+
+ component.update();
};
const clickPaginationNextButton = () => {
- testBed.find('remoteClusterListTable.pagination-button-next').simulate('click');
+ const { find, component } = testBed;
+
+ act(() => {
+ find('remoteClusterListTable.pagination-button-next').simulate('click');
+ });
+
+ component.update();
};
return {
diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js
index d75921c5f49f2..765da32260eb7 100644
--- a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js
+++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js
@@ -10,25 +10,23 @@ import { getRemoteClusterMock } from '../../../fixtures/remote_cluster';
import { PROXY_MODE } from '../../../common/constants';
-import { setupEnvironment, nextTick, getRandomString, findTestSubject } from '../helpers';
+import { setupEnvironment, getRandomString, findTestSubject } from '../helpers';
import { setup } from './remote_clusters_list.helpers';
describe(' ', () => {
- let server;
- let httpRequestsMockHelpers;
+ const { server, httpRequestsMockHelpers } = setupEnvironment();
beforeAll(() => {
- ({ server, httpRequestsMockHelpers } = setupEnvironment());
+ jest.useFakeTimers();
});
afterAll(() => {
+ jest.useRealTimers();
server.restore();
});
- beforeEach(() => {
- httpRequestsMockHelpers.setLoadRemoteClustersResponse([]);
- });
+ httpRequestsMockHelpers.setLoadRemoteClustersResponse([]);
describe('on component mount', () => {
let exists;
@@ -47,9 +45,10 @@ describe(' ', () => {
let component;
beforeEach(async () => {
- ({ exists, component } = setup());
+ await act(async () => {
+ ({ exists, component } = setup());
+ });
- await nextTick(100); // We need to wait next tick for the mock server response to kick in
component.update();
});
@@ -66,7 +65,7 @@ describe(' ', () => {
let find;
let table;
let actions;
- let waitFor;
+ let component;
let form;
const remoteClusters = [
@@ -87,9 +86,10 @@ describe(' ', () => {
httpRequestsMockHelpers.setLoadRemoteClustersResponse(remoteClusters);
await act(async () => {
- ({ find, table, actions, waitFor, form } = setup());
- await waitFor('remoteClusterListTable');
+ ({ find, table, actions, form, component } = setup());
});
+
+ component.update();
});
test('pagination works', () => {
@@ -117,7 +117,6 @@ describe(' ', () => {
let actions;
let tableCellsValues;
let rows;
- let waitFor;
// For deterministic tests, we need to make sure that remoteCluster1 comes before remoteCluster2
// in the table list that is rendered. As the table orders alphabetically by index name
@@ -151,11 +150,11 @@ describe(' ', () => {
httpRequestsMockHelpers.setLoadRemoteClustersResponse(remoteClusters);
await act(async () => {
- ({ component, find, exists, table, actions, waitFor } = setup());
-
- await waitFor('remoteClusterListTable');
+ ({ component, find, exists, table, actions } = setup());
});
+ component.update();
+
// Read the remote clusters list table
({ rows, tableCellsValues } = table.getMetaData('remoteClusterListTable'));
});
@@ -282,10 +281,11 @@ describe(' ', () => {
actions.clickConfirmModalDeleteRemoteCluster();
await act(async () => {
- await nextTick(600); // there is a 500ms timeout in the api action
- component.update();
+ jest.advanceTimersByTime(600); // there is a 500ms timeout in the api action
});
+ component.update();
+
({ rows } = table.getMetaData('remoteClusterListTable'));
expect(rows.length).toBe(2);
diff --git a/x-pack/plugins/security/common/model/authenticated_user.mock.ts b/x-pack/plugins/security/common/model/authenticated_user.mock.ts
index 0393c94da8d40..d9947bc96b78b 100644
--- a/x-pack/plugins/security/common/model/authenticated_user.mock.ts
+++ b/x-pack/plugins/security/common/model/authenticated_user.mock.ts
@@ -15,7 +15,7 @@ export function mockAuthenticatedUser(user: Partial = {}) {
enabled: true,
authentication_realm: { name: 'native1', type: 'native' },
lookup_realm: { name: 'native1', type: 'native' },
- authentication_provider: 'basic1',
+ authentication_provider: { type: 'basic', name: 'basic1' },
authentication_type: 'realm',
...user,
};
diff --git a/x-pack/plugins/security/common/model/authenticated_user.test.ts b/x-pack/plugins/security/common/model/authenticated_user.test.ts
index cdf1423df56df..d253fed97f353 100644
--- a/x-pack/plugins/security/common/model/authenticated_user.test.ts
+++ b/x-pack/plugins/security/common/model/authenticated_user.test.ts
@@ -12,6 +12,7 @@ describe('#canUserChangePassword', () => {
expect(
canUserChangePassword({
username: 'foo',
+ authentication_provider: { type: 'basic', name: 'basic1' },
authentication_realm: {
name: 'the realm name',
type: realm,
@@ -25,6 +26,7 @@ describe('#canUserChangePassword', () => {
expect(
canUserChangePassword({
username: 'foo',
+ authentication_provider: { type: 'the provider type', name: 'does not matter' },
authentication_realm: {
name: 'the realm name',
type: 'does not matter',
diff --git a/x-pack/plugins/security/common/model/authenticated_user.ts b/x-pack/plugins/security/common/model/authenticated_user.ts
index 5ea420af202dc..d5c8d4e474c60 100644
--- a/x-pack/plugins/security/common/model/authenticated_user.ts
+++ b/x-pack/plugins/security/common/model/authenticated_user.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import type { AuthenticationProvider } from '../types';
import { User } from './user';
const REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE = ['reserved', 'native'];
@@ -28,9 +29,9 @@ export interface AuthenticatedUser extends User {
lookup_realm: UserRealm;
/**
- * Name of the Kibana authentication provider that used to authenticate user.
+ * The authentication provider that used to authenticate user.
*/
- authentication_provider: string;
+ authentication_provider: AuthenticationProvider;
/**
* The AuthenticationType used by ES to authenticate the user.
diff --git a/x-pack/plugins/security/server/audit/audit_events.test.ts b/x-pack/plugins/security/server/audit/audit_events.test.ts
index 1713badede2f7..c826bb1d33f99 100644
--- a/x-pack/plugins/security/server/audit/audit_events.test.ts
+++ b/x-pack/plugins/security/server/audit/audit_events.test.ts
@@ -106,6 +106,63 @@ describe('#savedObjectEvent', () => {
`);
});
+ test('does create event for read access of saved objects', () => {
+ expect(
+ savedObjectEvent({
+ action: SavedObjectAction.GET,
+ savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' },
+ })
+ ).not.toBeUndefined();
+ expect(
+ savedObjectEvent({
+ action: SavedObjectAction.FIND,
+ savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' },
+ })
+ ).not.toBeUndefined();
+ });
+
+ test('does not create event for read access of config or telemetry objects', () => {
+ expect(
+ savedObjectEvent({
+ action: SavedObjectAction.GET,
+ savedObject: { type: 'config', id: 'SAVED_OBJECT_ID' },
+ })
+ ).toBeUndefined();
+ expect(
+ savedObjectEvent({
+ action: SavedObjectAction.GET,
+ savedObject: { type: 'telemetry', id: 'SAVED_OBJECT_ID' },
+ })
+ ).toBeUndefined();
+ expect(
+ savedObjectEvent({
+ action: SavedObjectAction.FIND,
+ savedObject: { type: 'config', id: 'SAVED_OBJECT_ID' },
+ })
+ ).toBeUndefined();
+ expect(
+ savedObjectEvent({
+ action: SavedObjectAction.FIND,
+ savedObject: { type: 'telemetry', id: 'SAVED_OBJECT_ID' },
+ })
+ ).toBeUndefined();
+ });
+
+ test('does create event for write access of config or telemetry objects', () => {
+ expect(
+ savedObjectEvent({
+ action: SavedObjectAction.UPDATE,
+ savedObject: { type: 'config', id: 'SAVED_OBJECT_ID' },
+ })
+ ).not.toBeUndefined();
+ expect(
+ savedObjectEvent({
+ action: SavedObjectAction.UPDATE,
+ savedObject: { type: 'telemetry', id: 'SAVED_OBJECT_ID' },
+ })
+ ).not.toBeUndefined();
+ });
+
test('creates event with `success` outcome for `REMOVE_REFERENCES` action', () => {
expect(
savedObjectEvent({
diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts
index e3c1f95349c92..6aba78c936071 100644
--- a/x-pack/plugins/security/server/audit/audit_events.ts
+++ b/x-pack/plugins/security/server/audit/audit_events.ts
@@ -220,7 +220,7 @@ export function savedObjectEvent({
deleteFromSpaces,
outcome,
error,
-}: SavedObjectParams): AuditEvent {
+}: SavedObjectParams): AuditEvent | undefined {
const doc = savedObject ? `${savedObject.type} [id=${savedObject.id}]` : 'saved objects';
const [present, progressive, past] = eventVerbs[action];
const message = error
@@ -230,6 +230,14 @@ export function savedObjectEvent({
: `User has ${past} ${doc}`;
const type = eventTypes[action];
+ if (
+ type === EventType.ACCESS &&
+ savedObject &&
+ (savedObject.type === 'config' || savedObject.type === 'telemetry')
+ ) {
+ return;
+ }
+
return {
message,
event: {
diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts
index e0dd98c7de639..9b30d4dbba456 100644
--- a/x-pack/plugins/security/server/audit/audit_service.test.ts
+++ b/x-pack/plugins/security/server/audit/audit_service.test.ts
@@ -130,6 +130,26 @@ describe('#asScoped', () => {
audit.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } });
expect(logger.info).not.toHaveBeenCalled();
});
+
+ it('does not log to audit logger if no event was generated', async () => {
+ const audit = new AuditService(logger).setup({
+ license,
+ config: {
+ enabled: true,
+ ignore_filters: [{ actions: ['ACTION'] }],
+ },
+ logging,
+ http,
+ getCurrentUser,
+ getSpaceId,
+ });
+ const request = httpServerMock.createKibanaRequest({
+ kibanaRequestState: { requestId: 'REQUEST_ID', requestUuid: 'REQUEST_UUID' },
+ });
+
+ audit.asScoped(request).log(undefined);
+ expect(logger.info).not.toHaveBeenCalled();
+ });
});
describe('#createLoggingConfig', () => {
diff --git a/x-pack/plugins/security/server/audit/audit_service.ts b/x-pack/plugins/security/server/audit/audit_service.ts
index 31c7e28be3b8c..744e4af56c861 100644
--- a/x-pack/plugins/security/server/audit/audit_service.ts
+++ b/x-pack/plugins/security/server/audit/audit_service.ts
@@ -27,7 +27,7 @@ export interface LegacyAuditLogger {
}
export interface AuditLogger {
- log: (event: AuditEvent) => void;
+ log: (event: AuditEvent | undefined) => void;
}
interface AuditLogMeta extends AuditEvent {
@@ -127,7 +127,10 @@ export class AuditService {
* });
* ```
*/
- const log = (event: AuditEvent) => {
+ const log: AuditLogger['log'] = (event) => {
+ if (!event) {
+ return;
+ }
const user = getCurrentUser(request);
const spaceId = getSpaceId(request);
const meta: AuditLogMeta = {
diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts
index 80aeb4f8b2959..eef45598d1761 100644
--- a/x-pack/plugins/security/server/authentication/authenticator.ts
+++ b/x-pack/plugins/security/server/authentication/authenticator.ts
@@ -10,14 +10,14 @@ import {
ILegacyClusterClient,
IBasePath,
} from '../../../../../src/core/server';
-import { SecurityLicense } from '../../common/licensing';
-import { AuthenticatedUser } from '../../common/model';
-import { AuthenticationProvider } from '../../common/types';
+import type { SecurityLicense } from '../../common/licensing';
+import type { AuthenticatedUser } from '../../common/model';
+import type { AuthenticationProvider } from '../../common/types';
import { SecurityAuditLogger, AuditServiceSetup, userLoginEvent } from '../audit';
-import { ConfigType } from '../config';
+import type { ConfigType } from '../config';
import { getErrorStatusCode } from '../errors';
-import { SecurityFeatureUsageServiceStart } from '../feature_usage';
-import { SessionValue, Session } from '../session_management';
+import type { SecurityFeatureUsageServiceStart } from '../feature_usage';
+import type { SessionValue, Session } from '../session_management';
import {
AuthenticationProviderOptions,
@@ -259,7 +259,7 @@ export class Authenticator {
isLoginAttemptWithProviderName(attempt) && this.providers.has(attempt.provider.name)
? [[attempt.provider.name, this.providers.get(attempt.provider.name)!]]
: isLoginAttemptWithProviderType(attempt)
- ? [...this.providerIterator(existingSessionValue)].filter(
+ ? [...this.providerIterator(existingSessionValue?.provider.name)].filter(
([, { type }]) => type === attempt.provider.type
)
: [];
@@ -338,7 +338,9 @@ export class Authenticator {
);
}
- for (const [providerName, provider] of this.providerIterator(existingSessionValue)) {
+ for (const [providerName, provider] of this.providerIterator(
+ existingSessionValue?.provider.name
+ )) {
// Check if current session has been set by this provider.
const ownsSession =
existingSessionValue?.provider.name === providerName &&
@@ -395,7 +397,7 @@ export class Authenticator {
// active session already some providers can still properly respond to the 3rd-party logout
// request. For example SAML provider can process logout request encoded in `SAMLRequest`
// query string parameter.
- for (const [, provider] of this.providerIterator(null)) {
+ for (const [, provider] of this.providerIterator()) {
const deauthenticationResult = await provider.logout(request);
if (!deauthenticationResult.notHandled()) {
return deauthenticationResult;
@@ -473,22 +475,22 @@ export class Authenticator {
}
/**
- * Returns provider iterator where providers are sorted in the order of priority (based on the session ownership).
- * @param sessionValue Current session value.
+ * Returns provider iterator starting from the suggested provider if any.
+ * @param suggestedProviderName Optional name of the provider to return first.
*/
private *providerIterator(
- sessionValue: SessionValue | null
+ suggestedProviderName?: string | null
): IterableIterator<[string, BaseAuthenticationProvider]> {
- // If there is no session to predict which provider to use first, let's use the order
- // providers are configured in. Otherwise return provider that owns session first, and only then the rest
+ // If there is no provider suggested or suggested provider isn't configured, let's use the order
+ // providers are configured in. Otherwise return suggested provider first, and only then the rest
// of providers.
- if (!sessionValue) {
+ if (!suggestedProviderName || !this.providers.has(suggestedProviderName)) {
yield* this.providers;
} else {
- yield [sessionValue.provider.name, this.providers.get(sessionValue.provider.name)!];
+ yield [suggestedProviderName, this.providers.get(suggestedProviderName)!];
for (const [providerName, provider] of this.providers) {
- if (providerName !== sessionValue.provider.name) {
+ if (providerName !== suggestedProviderName) {
yield [providerName, provider];
}
}
diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts
index 6b13c32a2f1ed..030b2a6e968af 100644
--- a/x-pack/plugins/security/server/authentication/providers/base.ts
+++ b/x-pack/plugins/security/server/authentication/providers/base.ts
@@ -113,7 +113,7 @@ export abstract class BaseAuthenticationProvider {
...(await this.options.client
.asScoped({ headers: { ...request.headers, ...authHeaders } })
.callAsCurrentUser('shield.authenticate')),
- authentication_provider: this.options.name,
+ authentication_provider: { type: this.type, name: this.options.name },
} as AuthenticatedUser);
}
}
diff --git a/x-pack/plugins/security/server/authentication/providers/http.test.ts b/x-pack/plugins/security/server/authentication/providers/http.test.ts
index c221ecd3f1e20..512a8ead2c32b 100644
--- a/x-pack/plugins/security/server/authentication/providers/http.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/http.test.ts
@@ -136,7 +136,10 @@ describe('HTTPAuthenticationProvider', () => {
});
await expect(provider.authenticate(request)).resolves.toEqual(
- AuthenticationResult.succeeded({ ...user, authentication_provider: 'http' })
+ AuthenticationResult.succeeded({
+ ...user,
+ authentication_provider: { type: 'http', name: 'http' },
+ })
);
expectAuthenticateCall(mockOptions.client, { headers: { authorization: header } });
diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts
index ee89af10cbeba..4ea395e7b53de 100644
--- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts
@@ -128,7 +128,7 @@ describe('KerberosAuthenticationProvider', () => {
await expect(operation(request)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'kerberos' },
+ { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } },
{
authHeaders: { authorization: 'Bearer some-token' },
state: { accessToken: 'some-token', refreshToken: 'some-refresh-token' },
@@ -164,7 +164,7 @@ describe('KerberosAuthenticationProvider', () => {
await expect(operation(request)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'kerberos' },
+ { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } },
{
authHeaders: { authorization: 'Bearer some-token' },
authResponseHeaders: { 'WWW-Authenticate': 'Negotiate response-token' },
@@ -361,7 +361,7 @@ describe('KerberosAuthenticationProvider', () => {
await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'kerberos' },
+ { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } },
{ authHeaders: { authorization } }
)
);
@@ -401,7 +401,7 @@ describe('KerberosAuthenticationProvider', () => {
await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'kerberos' },
+ { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } },
{
authHeaders: { authorization: 'Bearer newfoo' },
state: { accessToken: 'newfoo', refreshToken: 'newbar' },
diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts
index fe46b159833bc..dfea7e508b333 100644
--- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts
@@ -29,7 +29,7 @@ describe('OIDCAuthenticationProvider', () => {
mockOptions = mockAuthenticationProviderOptions({ name: 'oidc' });
mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient();
- mockUser = mockAuthenticatedUser({ authentication_provider: 'oidc' });
+ mockUser = mockAuthenticatedUser({ authentication_provider: { type: 'oidc', name: 'oidc' } });
mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method) => {
if (method === 'shield.authenticate') {
return mockUser;
diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts
index 7e76f4c81998d..969682b225ceb 100644
--- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts
@@ -127,7 +127,7 @@ describe('PKIAuthenticationProvider', () => {
await expect(operation(request)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'pki' },
+ { ...user, authentication_provider: { type: 'pki', name: 'pki' } },
{
authHeaders: { authorization: 'Bearer access-token' },
state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' },
@@ -169,7 +169,7 @@ describe('PKIAuthenticationProvider', () => {
await expect(operation(request)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'pki' },
+ { ...user, authentication_provider: { type: 'pki', name: 'pki' } },
{
authHeaders: { authorization: 'Bearer access-token' },
state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' },
@@ -356,7 +356,7 @@ describe('PKIAuthenticationProvider', () => {
await expect(provider.authenticate(request, state)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'pki' },
+ { ...user, authentication_provider: { type: 'pki', name: 'pki' } },
{
authHeaders: { authorization: 'Bearer access-token' },
state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' },
@@ -405,7 +405,7 @@ describe('PKIAuthenticationProvider', () => {
await expect(provider.authenticate(request, state)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'pki' },
+ { ...user, authentication_provider: { type: 'pki', name: 'pki' } },
{
authHeaders: { authorization: 'Bearer access-token' },
state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' },
@@ -486,7 +486,7 @@ describe('PKIAuthenticationProvider', () => {
await expect(provider.authenticate(request, state)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'pki' },
+ { ...user, authentication_provider: { type: 'pki', name: 'pki' } },
{ authHeaders: { authorization: `Bearer ${state.accessToken}` } }
)
);
diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts
index d160b79bf659d..a1f2e99c13357 100644
--- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts
@@ -28,7 +28,7 @@ describe('SAMLAuthenticationProvider', () => {
mockOptions = mockAuthenticationProviderOptions({ name: 'saml' });
mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient();
- mockUser = mockAuthenticatedUser({ authentication_provider: 'saml' });
+ mockUser = mockAuthenticatedUser({ authentication_provider: { type: 'saml', name: 'saml' } });
mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method) => {
if (method === 'shield.authenticate') {
return mockUser;
@@ -490,7 +490,9 @@ describe('SAMLAuthenticationProvider', () => {
for (const [description, response] of [
[
'current session is valid',
- Promise.resolve(mockAuthenticatedUser({ authentication_provider: 'saml' })),
+ Promise.resolve(
+ mockAuthenticatedUser({ authentication_provider: { type: 'saml', name: 'saml' } })
+ ),
],
[
'current session is is expired',
diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts
index 8cd11bce98269..4501004ab69c1 100644
--- a/x-pack/plugins/security/server/authentication/providers/token.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts
@@ -60,7 +60,7 @@ describe('TokenAuthenticationProvider', () => {
await expect(provider.login(request, credentials)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'token' },
+ { ...user, authentication_provider: { type: 'token', name: 'token' } },
{ authHeaders: { authorization }, state: tokenPair }
)
);
@@ -196,7 +196,7 @@ describe('TokenAuthenticationProvider', () => {
await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'token' },
+ { ...user, authentication_provider: { type: 'token', name: 'token' } },
{ authHeaders: { authorization } }
)
);
@@ -236,7 +236,7 @@ describe('TokenAuthenticationProvider', () => {
await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
AuthenticationResult.succeeded(
- { ...user, authentication_provider: 'token' },
+ { ...user, authentication_provider: { type: 'token', name: 'token' } },
{
authHeaders: { authorization: 'Bearer newfoo' },
state: { accessToken: 'newfoo', refreshToken: 'newbar' },
diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts
index 32b8708d2b381..e75c0d1c4085f 100644
--- a/x-pack/plugins/security/server/config.test.ts
+++ b/x-pack/plugins/security/server/config.test.ts
@@ -36,6 +36,10 @@ describe('config schema', () => {
"hint": undefined,
"icon": undefined,
"order": 0,
+ "session": Object {
+ "idleTimeout": undefined,
+ "lifespan": undefined,
+ },
"showInSelector": true,
},
},
@@ -54,8 +58,6 @@ describe('config schema', () => {
"secureCookies": false,
"session": Object {
"cleanupInterval": "PT1H",
- "idleTimeout": null,
- "lifespan": null,
},
}
`);
@@ -82,6 +84,10 @@ describe('config schema', () => {
"hint": undefined,
"icon": undefined,
"order": 0,
+ "session": Object {
+ "idleTimeout": undefined,
+ "lifespan": undefined,
+ },
"showInSelector": true,
},
},
@@ -100,8 +106,6 @@ describe('config schema', () => {
"secureCookies": false,
"session": Object {
"cleanupInterval": "PT1H",
- "idleTimeout": null,
- "lifespan": null,
},
}
`);
@@ -128,6 +132,10 @@ describe('config schema', () => {
"hint": undefined,
"icon": undefined,
"order": 0,
+ "session": Object {
+ "idleTimeout": undefined,
+ "lifespan": undefined,
+ },
"showInSelector": true,
},
},
@@ -145,8 +153,6 @@ describe('config schema', () => {
"secureCookies": false,
"session": Object {
"cleanupInterval": "PT1H",
- "idleTimeout": null,
- "lifespan": null,
},
}
`);
@@ -387,6 +393,35 @@ describe('config schema', () => {
"enabled": true,
"icon": "logoElasticsearch",
"order": 0,
+ "session": Object {},
+ "showInSelector": true,
+ },
+ },
+ }
+ `);
+ });
+
+ it('can be successfully validated with session config overrides', () => {
+ expect(
+ ConfigSchema.validate({
+ authc: {
+ providers: {
+ basic: { basic1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } },
+ },
+ },
+ }).authc.providers
+ ).toMatchInlineSnapshot(`
+ Object {
+ "basic": Object {
+ "basic1": Object {
+ "description": "Log in with Elasticsearch",
+ "enabled": true,
+ "icon": "logoElasticsearch",
+ "order": 0,
+ "session": Object {
+ "idleTimeout": "PT0.123S",
+ "lifespan": "PT0.546S",
+ },
"showInSelector": true,
},
},
@@ -439,6 +474,35 @@ describe('config schema', () => {
"enabled": true,
"icon": "logoElasticsearch",
"order": 0,
+ "session": Object {},
+ "showInSelector": true,
+ },
+ },
+ }
+ `);
+ });
+
+ it('can be successfully validated with session config overrides', () => {
+ expect(
+ ConfigSchema.validate({
+ authc: {
+ providers: {
+ token: { token1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } },
+ },
+ },
+ }).authc.providers
+ ).toMatchInlineSnapshot(`
+ Object {
+ "token": Object {
+ "token1": Object {
+ "description": "Log in with Elasticsearch",
+ "enabled": true,
+ "icon": "logoElasticsearch",
+ "order": 0,
+ "session": Object {
+ "idleTimeout": "PT0.123S",
+ "lifespan": "PT0.546S",
+ },
"showInSelector": true,
},
},
@@ -477,6 +541,33 @@ describe('config schema', () => {
"pki1": Object {
"enabled": true,
"order": 0,
+ "session": Object {},
+ "showInSelector": true,
+ },
+ },
+ }
+ `);
+ });
+
+ it('can be successfully validated with session config overrides', () => {
+ expect(
+ ConfigSchema.validate({
+ authc: {
+ providers: {
+ pki: { pki1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } },
+ },
+ },
+ }).authc.providers
+ ).toMatchInlineSnapshot(`
+ Object {
+ "pki": Object {
+ "pki1": Object {
+ "enabled": true,
+ "order": 0,
+ "session": Object {
+ "idleTimeout": "PT0.123S",
+ "lifespan": "PT0.546S",
+ },
"showInSelector": true,
},
},
@@ -517,6 +608,33 @@ describe('config schema', () => {
"kerberos1": Object {
"enabled": true,
"order": 0,
+ "session": Object {},
+ "showInSelector": true,
+ },
+ },
+ }
+ `);
+ });
+
+ it('can be successfully validated with session config overrides', () => {
+ expect(
+ ConfigSchema.validate({
+ authc: {
+ providers: {
+ kerberos: { kerberos1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } },
+ },
+ },
+ }).authc.providers
+ ).toMatchInlineSnapshot(`
+ Object {
+ "kerberos": Object {
+ "kerberos1": Object {
+ "enabled": true,
+ "order": 0,
+ "session": Object {
+ "idleTimeout": "PT0.123S",
+ "lifespan": "PT0.546S",
+ },
"showInSelector": true,
},
},
@@ -562,12 +680,53 @@ describe('config schema', () => {
"enabled": true,
"order": 0,
"realm": "oidc1",
+ "session": Object {},
"showInSelector": true,
},
"oidc2": Object {
"enabled": true,
"order": 1,
"realm": "oidc2",
+ "session": Object {},
+ "showInSelector": true,
+ },
+ },
+ }
+ `);
+ });
+
+ it('can be successfully validated with session config overrides', () => {
+ expect(
+ ConfigSchema.validate({
+ authc: {
+ providers: {
+ oidc: {
+ oidc1: { order: 0, realm: 'oidc1', session: { idleTimeout: 123 } },
+ oidc2: { order: 1, realm: 'oidc2', session: { idleTimeout: 321, lifespan: 546 } },
+ },
+ },
+ },
+ }).authc.providers
+ ).toMatchInlineSnapshot(`
+ Object {
+ "oidc": Object {
+ "oidc1": Object {
+ "enabled": true,
+ "order": 0,
+ "realm": "oidc1",
+ "session": Object {
+ "idleTimeout": "PT0.123S",
+ },
+ "showInSelector": true,
+ },
+ "oidc2": Object {
+ "enabled": true,
+ "order": 1,
+ "realm": "oidc2",
+ "session": Object {
+ "idleTimeout": "PT0.321S",
+ "lifespan": "PT0.546S",
+ },
"showInSelector": true,
},
},
@@ -617,6 +776,62 @@ describe('config schema', () => {
"enabled": true,
"order": 0,
"realm": "saml1",
+ "session": Object {},
+ "showInSelector": true,
+ "useRelayStateDeepLink": false,
+ },
+ "saml2": Object {
+ "enabled": true,
+ "maxRedirectURLSize": ByteSizeValue {
+ "valueInBytes": 1024,
+ },
+ "order": 1,
+ "realm": "saml2",
+ "session": Object {},
+ "showInSelector": true,
+ "useRelayStateDeepLink": false,
+ },
+ "saml3": Object {
+ "enabled": true,
+ "order": 2,
+ "realm": "saml3",
+ "session": Object {},
+ "showInSelector": true,
+ "useRelayStateDeepLink": true,
+ },
+ },
+ }
+ `);
+ });
+
+ it('can be successfully validated with session config overrides', () => {
+ expect(
+ ConfigSchema.validate({
+ authc: {
+ providers: {
+ saml: {
+ saml1: { order: 0, realm: 'saml1', session: { idleTimeout: 123 } },
+ saml2: {
+ order: 1,
+ realm: 'saml2',
+ maxRedirectURLSize: '1kb',
+ session: { idleTimeout: 321, lifespan: 546 },
+ },
+ saml3: { order: 2, realm: 'saml3', useRelayStateDeepLink: true },
+ },
+ },
+ },
+ }).authc.providers
+ ).toMatchInlineSnapshot(`
+ Object {
+ "saml": Object {
+ "saml1": Object {
+ "enabled": true,
+ "order": 0,
+ "realm": "saml1",
+ "session": Object {
+ "idleTimeout": "PT0.123S",
+ },
"showInSelector": true,
"useRelayStateDeepLink": false,
},
@@ -627,6 +842,10 @@ describe('config schema', () => {
},
"order": 1,
"realm": "saml2",
+ "session": Object {
+ "idleTimeout": "PT0.321S",
+ "lifespan": "PT0.546S",
+ },
"showInSelector": true,
"useRelayStateDeepLink": false,
},
@@ -634,6 +853,7 @@ describe('config schema', () => {
"enabled": true,
"order": 2,
"realm": "saml3",
+ "session": Object {},
"showInSelector": true,
"useRelayStateDeepLink": true,
},
@@ -701,6 +921,7 @@ describe('config schema', () => {
"enabled": true,
"icon": "logoElasticsearch",
"order": 0,
+ "session": Object {},
"showInSelector": true,
},
"basic2": Object {
@@ -708,6 +929,7 @@ describe('config schema', () => {
"enabled": false,
"icon": "logoElasticsearch",
"order": 1,
+ "session": Object {},
"showInSelector": true,
},
},
@@ -716,6 +938,7 @@ describe('config schema', () => {
"enabled": false,
"order": 3,
"realm": "saml3",
+ "session": Object {},
"showInSelector": true,
"useRelayStateDeepLink": false,
},
@@ -723,6 +946,7 @@ describe('config schema', () => {
"enabled": true,
"order": 1,
"realm": "saml1",
+ "session": Object {},
"showInSelector": true,
"useRelayStateDeepLink": false,
},
@@ -730,6 +954,7 @@ describe('config schema', () => {
"enabled": true,
"order": 2,
"realm": "saml2",
+ "session": Object {},
"showInSelector": true,
"useRelayStateDeepLink": false,
},
@@ -1089,4 +1314,314 @@ describe('createConfig()', () => {
'[audit]: xpack.security.audit.ignore_filters can only be used with the ECS audit logger. To enable the ECS audit logger, specify where you want to write the audit events using xpack.security.audit.appender.'
);
});
+
+ describe('#getExpirationTimeouts', () => {
+ function createMockConfig(config: Record = {}) {
+ return createConfig(ConfigSchema.validate(config), loggingSystemMock.createLogger(), {
+ isTLSEnabled: false,
+ });
+ }
+
+ it('returns default values if neither global nor provider specific settings are set', async () => {
+ expect(createMockConfig().session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": null,
+ "lifespan": null,
+ }
+ `);
+ });
+
+ it('correctly handles explicitly disabled global settings', async () => {
+ expect(
+ createMockConfig({
+ session: { idleTimeout: null, lifespan: null },
+ }).session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": null,
+ "lifespan": null,
+ }
+ `);
+
+ expect(
+ createMockConfig({
+ session: { idleTimeout: 0, lifespan: 0 },
+ }).session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": null,
+ "lifespan": null,
+ }
+ `);
+ });
+
+ it('falls back to the global settings if provider does not override them', async () => {
+ expect(
+ createMockConfig({ session: { idleTimeout: 123 } }).session.getExpirationTimeouts({
+ type: 'basic',
+ name: 'basic1',
+ })
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT0.123S",
+ "lifespan": null,
+ }
+ `);
+
+ expect(
+ createMockConfig({ session: { lifespan: 456 } }).session.getExpirationTimeouts({
+ type: 'basic',
+ name: 'basic1',
+ })
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": null,
+ "lifespan": "PT0.456S",
+ }
+ `);
+
+ expect(
+ createMockConfig({
+ session: { idleTimeout: 123, lifespan: 456 },
+ }).session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT0.123S",
+ "lifespan": "PT0.456S",
+ }
+ `);
+ });
+
+ it('falls back to the global settings if provider is not known', async () => {
+ expect(
+ createMockConfig({ session: { idleTimeout: 123 } }).session.getExpirationTimeouts({
+ type: 'some type',
+ name: 'some name',
+ })
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT0.123S",
+ "lifespan": null,
+ }
+ `);
+
+ expect(
+ createMockConfig({ session: { lifespan: 456 } }).session.getExpirationTimeouts({
+ type: 'some type',
+ name: 'some name',
+ })
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": null,
+ "lifespan": "PT0.456S",
+ }
+ `);
+
+ expect(
+ createMockConfig({
+ session: { idleTimeout: 123, lifespan: 456 },
+ }).session.getExpirationTimeouts({ type: 'some type', name: 'some name' })
+ ).toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT0.123S",
+ "lifespan": "PT0.456S",
+ }
+ `);
+ });
+
+ it('uses provider overrides if specified (only idle timeout)', async () => {
+ const configWithoutGlobal = createMockConfig({
+ authc: {
+ providers: {
+ basic: { basic1: { order: 0, session: { idleTimeout: 321 } } },
+ saml: { saml1: { order: 1, realm: 'saml-realm', session: { idleTimeout: 332211 } } },
+ },
+ },
+ session: { idleTimeout: null },
+ });
+ expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT0.321S",
+ "lifespan": null,
+ }
+ `);
+ expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT5M32.211S",
+ "lifespan": null,
+ }
+ `);
+
+ const configWithGlobal = createMockConfig({
+ authc: {
+ providers: {
+ basic: { basic1: { order: 0, session: { idleTimeout: 321 } } },
+ saml: { saml1: { order: 1, realm: 'saml-realm', session: { idleTimeout: 332211 } } },
+ },
+ },
+ session: { idleTimeout: 123 },
+ });
+ expect(configWithGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT0.321S",
+ "lifespan": null,
+ }
+ `);
+ expect(configWithGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT5M32.211S",
+ "lifespan": null,
+ }
+ `);
+ });
+
+ it('uses provider overrides if specified (only lifespan)', async () => {
+ const configWithoutGlobal = createMockConfig({
+ authc: {
+ providers: {
+ basic: { basic1: { order: 0, session: { lifespan: 654 } } },
+ saml: { saml1: { order: 1, realm: 'saml-realm', session: { lifespan: 665544 } } },
+ },
+ },
+ session: { lifespan: null },
+ });
+ expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": null,
+ "lifespan": "PT0.654S",
+ }
+ `);
+ expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": null,
+ "lifespan": "PT11M5.544S",
+ }
+ `);
+
+ const configWithGlobal = createMockConfig({
+ authc: {
+ providers: {
+ basic: { basic1: { order: 0, session: { lifespan: 654 } } },
+ saml: { saml1: { order: 1, realm: 'saml-realm', session: { idleTimeout: 665544 } } },
+ },
+ },
+ session: { lifespan: 456 },
+ });
+ expect(configWithGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": null,
+ "lifespan": "PT0.654S",
+ }
+ `);
+ expect(configWithGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT11M5.544S",
+ "lifespan": "PT0.456S",
+ }
+ `);
+ });
+
+ it('uses provider overrides if specified (both idle timeout and lifespan)', async () => {
+ const configWithoutGlobal = createMockConfig({
+ authc: {
+ providers: {
+ basic: { basic1: { order: 0, session: { idleTimeout: 321, lifespan: 654 } } },
+ saml: {
+ saml1: {
+ order: 1,
+ realm: 'saml-realm',
+ session: { idleTimeout: 332211, lifespan: 665544 },
+ },
+ },
+ },
+ },
+ session: { idleTimeout: null, lifespan: null },
+ });
+ expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT0.321S",
+ "lifespan": "PT0.654S",
+ }
+ `);
+ expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT5M32.211S",
+ "lifespan": "PT11M5.544S",
+ }
+ `);
+
+ const configWithGlobal = createMockConfig({
+ authc: {
+ providers: {
+ basic: { basic1: { order: 0, session: { idleTimeout: 321, lifespan: 654 } } },
+ saml: {
+ saml1: {
+ order: 1,
+ realm: 'saml-realm',
+ session: { idleTimeout: 332211, lifespan: 665544 },
+ },
+ },
+ },
+ },
+ session: { idleTimeout: 123, lifespan: 456 },
+ });
+ expect(configWithGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT0.321S",
+ "lifespan": "PT0.654S",
+ }
+ `);
+ expect(configWithGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": "PT5M32.211S",
+ "lifespan": "PT11M5.544S",
+ }
+ `);
+ });
+
+ it('uses provider overrides if disabled (both idle timeout and lifespan)', async () => {
+ const config = createMockConfig({
+ authc: {
+ providers: {
+ basic: { basic1: { order: 0, session: { idleTimeout: null, lifespan: null } } },
+ saml: {
+ saml1: {
+ order: 1,
+ realm: 'saml-realm',
+ session: { idleTimeout: 0, lifespan: 0 },
+ },
+ },
+ },
+ },
+ session: { idleTimeout: 123, lifespan: 456 },
+ });
+ expect(config.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": null,
+ "lifespan": null,
+ }
+ `);
+ expect(config.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "idleTimeout": null,
+ "lifespan": null,
+ }
+ `);
+ });
+ });
});
diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts
index 80b46a67ce011..4da0a8598309a 100644
--- a/x-pack/plugins/security/server/config.ts
+++ b/x-pack/plugins/security/server/config.ts
@@ -5,11 +5,24 @@
*/
import crypto from 'crypto';
+import type { Duration } from 'moment';
import { schema, Type, TypeOf } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import { Logger, config as coreConfig } from '../../../../src/core/server';
+import type { AuthenticationProvider } from '../common/types';
export type ConfigType = ReturnType;
+type RawConfigType = TypeOf;
+
+interface ProvidersCommonConfigType {
+ enabled: Type;
+ showInSelector: Type;
+ order: Type;
+ description?: Type;
+ hint?: Type