diff --git a/.sass-lint.yml b/.sass-lint.yml
index d6eaaf391de1a..9eed50602f520 100644
--- a/.sass-lint.yml
+++ b/.sass-lint.yml
@@ -1,6 +1,7 @@
files:
include:
- 'src/legacy/core_plugins/metrics/**/*.s+(a|c)ss'
+ - 'src/plugins/index_pattern_management/**/*.s+(a|c)ss'
- 'src/plugins/timelion/**/*.s+(a|c)ss'
- 'src/plugins/vis_type_vislib/**/*.s+(a|c)ss'
- 'src/plugins/vis_type_vega/**/*.s+(a|c)ss'
diff --git a/docs/canvas/canvas-edit-workpads.asciidoc b/docs/canvas/canvas-edit-workpads.asciidoc
new file mode 100644
index 0000000000000..6558def8a7474
--- /dev/null
+++ b/docs/canvas/canvas-edit-workpads.asciidoc
@@ -0,0 +1,136 @@
+[role="xpack"]
+[[edit-workpads]]
+== Edit workpads
+
+To create the look and feel that you want, apply format settings to the entire workpad, or individual elements.
+
+[float]
+[[create-variables]]
+=== Create variables
+
+When you frequently use copy and paste, create variables to easily reuse strings and patterns. For example, when you clone a large workpad and need to connect your elements to a new index, use variables to update
+each element instead of updating them manually.
+
+. Create the variables.
+.. Click *Add a variable*.
+.. Specify the variable options, then click *Save changes*.
+
+. Apply the variable.
+.. Copy the variable.
+.. Select the element you want to change, then open the expression editor.
+.. Paste the variable.
+
+For example, to change the index pattern for a set of charts:
+
+Specify the variable options.
+
+[role="screenshot"]
+image::images/specify_variable_syntax.png[Specify the variable syntax]
+
+Copy the variable, then apply it to each element you want to update in the *Expression editor*.
+
+[role="screenshot"]
+image::images/copy_variable_syntax.png[Copy the variable syntax]
+
+[float]
+[[apply-changes-to-the-entire-workpad]]
+=== Apply changes to the entire workpad
+
+With stylesheets, you can change the look of the entire workpad, including fonts, colors, layout, and more.
+
+To get started, enter the changes you want to make in the *Global CSS overrides* text editor, then click *Apply stylesheet*.
+
+For example, to change the background for the entire workpad, enter:
+
+[source,text]
+--------------------------------------------------
+.canvasPage {
+background-color: #3990e6;
+}
+--------------------------------------------------
+
+[float]
+[[change-the-element-settings]]
+=== Change the element settings
+
+Element settings enable you to change the display options at the element level. For example, use the element settings to change the dimensions, style, or location of an element.
+
+[float]
+[[change-the-display-options]]
+==== Change the display options
+
+Choose the display options for your elements. The options available depend on the element you select.
+
+To change the element display options, click *Display*, then make your changes in the editor.
+
+To use CSS overrides:
+
+. Click *+* next to *Element style*, then select *CSS*.
+. In the *CSS* text editor, enter the changes you want to make, then click *Apply stylesheet*.
+
+For example, to center an element, enter:
+
+[source,text]
+--------------------------------------------------
+.canvasRenderEl h1 {
+text.align: center;
+}
+--------------------------------------------------
+
+[float]
+[[clone-elements]]
+==== Clone elements
+To use an element with the same functionality and appearance in multiple places, clone the element.
+
+Select the element, then click *Edit > Clone*.
+
+[role="screenshot"]
+image::images/clone_element.gif[Clone elements]
+
+[float]
+[[move-and-resize-elements]]
+==== Move and resize elements
+
+Canvas provides you with many options to move and resize the elements on your workpad.
+
+* To move elements, click and hold the element, then drag to the new location.
+
+* To move elements by 1 pixel, select the element, press and hold Shift, then use your arrow keys.
+
+* To move elements by 10 pixels, select the element, then use your arrow keys.
+
+* To resize elements, click and drag the resize handles to the new dimensions.
+
+[float]
+[[edit-elements]]
+==== Edit elements
+
+The element editing options allow you to arrange and organize the elements on your workpad page.
+
+To align two or more elements:
+
+. Press and hold Shift, then select the elements you want to align.
+
+. Click *Edit > Alignment*, then select the alignment option.
+
+To distribute three or more elements:
+
+. Press and hold Shift, then select the elements you want to distribute.
+
+. Click *Edit > Distribution*, then select the distribution option.
+
+To reorder elements:
+
+. Select the element you want to reorder.
+
+. Click *Edit > Order*, then select the order option.
+
+[float]
+[[delete-elements]]
+==== Delete elements
+
+When you no longer need an element, delete it from your workpad.
+
+. Select the element you want to delete.
+
+. Click *Edit > Delete*.
diff --git a/docs/canvas/canvas-elements.asciidoc b/docs/canvas/canvas-elements.asciidoc
deleted file mode 100644
index 9c7467bb452fd..0000000000000
--- a/docs/canvas/canvas-elements.asciidoc
+++ /dev/null
@@ -1,167 +0,0 @@
-[role="xpack"]
-[[add-canvas-elements]]
-=== Add elements
-
-Create a story about your data by adding elements to your workpad that include images, text, charts, and more. You can create your own elements and connect them to your data sources, add saved objects, and add your own images.
-
-[float]
-[[create-canvas-element]]
-==== Create an element
-
-Choose the type of element you want to use, then connect it to your own data.
-
-. Click *Add element*, then select the element you want to use.
-+
-[role="screenshot"]
-image::images/canvas-element-select.gif[Canvas elements]
-
-. To familiarize yourself with the element, use the preconfigured data demo data.
-+
-By default, most of the elements you create use demo data until you change the data source. The demo data includes a small data set that you can use to experiment with your element.
-
-. To connect the element to your data, select *Data*, then select one of the following data sources:
-
-* *{es} SQL* — Access your data in {es} using SQL syntax. For information about SQL syntax, refer to {ref}/sql-spec.html[SQL language].
-
-* *{es} documents* — Access your data in {es} without using aggregations. To use, select an index and fields, and optionally enter a query using the <>. Use the *{es} documents* data source when you have low volume datasets, to view raw documents, or to plot exact, non-aggregated values on a chart.
-
-* *Timelion* — Access your time series data using <> queries. To use Timelion queries, you can enter a query using the <>.
-
-Each element can display a different data source. Pages and workpads often contain multiple data sources.
-
-[float]
-[[canvas-add-object]]
-==== Add a saved object
-
-Add <> to your workpad, such as maps and visualizations.
-
-. Click *Add element > Add from Visualize Library*.
-
-. Select the saved object you want to add.
-+
-[role="screenshot"]
-image::images/canvas-map-embed.gif[]
-
-. To use the customization options, click the panel menu, then select one of the following options:
-
-* *Edit map* — Opens <> or <> so that you can edit the original saved object.
-
-* *Edit panel title* — Adds a title to the saved object.
-
-* *Customize time range* — Exposes a time filter dedicated to the saved object.
-
-* *Inspect* — Allows you to drill down into the element data.
-
-[float]
-[[canvas-add-image]]
-==== Add your own image
-
-To personalize your workpad, add your own logos and graphics.
-
-. Click *Add element > Manage assets*.
-
-. On the *Manage workpad assets* window, drag and drop your images.
-
-. To add the image to the workpad, click the *Create image element* icon.
-+
-[role="screenshot"]
-image::images/canvas-add-image.gif[]
-
-[float]
-[[move-canvas-elements]]
-==== Organize elements
-
-Move and resize your elements to meet your design needs.
-
-* To move, click and hold the element, then drag to the new location.
-
-* To move by 1 pixel, select the element, press and hold Shift, then use your arrow keys.
-
-* To move by 10 pixels, select the element, then use your arrow keys.
-
-* To resize, click and drag the resize handles to the new dimensions.
-
-[float]
-[[format-canvas-elements]]
-==== Format elements
-
-For consistency and readability across your workpad pages, align, distribute, and reorder elements.
-
-To align two or more elements:
-
-. Press and hold Shift, then select the elements you want to align.
-
-. Click *Edit > Alignment*, then select the alignment option.
-
-To distribute three or more elements:
-
-. Press and hold Shift, then select the elements you want to distribute.
-
-. Click *Edit > Distribution*, then select the distribution option.
-
-To reorder elements:
-
-. Select the element you want to reorder.
-
-. Click *Edit > Order*, then select the order option.
-
-[float]
-[[data-display]]
-==== Change the element display options
-
-Each element has its own display options to fit your design needs.
-
-To choose the display options, click *Display*, then make your changes.
-
-To define the appearance of the container and border:
-
-. Next to *Element style*, click *+*, then select *Container style*.
-
-. Expand *Container style*.
-
-. Change the *Appearance* and *Border* options.
-
-To apply CSS overrides:
-
-. Next to *Element style*, click *+*, then select *CSS*.
-
-. Enter the *CSS*.
-+
-For example, to center the Markdown element, enter:
-+
-[source,text]
---------------------------------------------------
-.canvasRenderEl h1 {
-text.align: center;
-}
---------------------------------------------------
-
-. Click *Apply stylesheet*.
-
-[float]
-[[save-elements]]
-==== Save elements
-
-To use the elements across all workpads, save the elements.
-
-When you're ready to save your element, select the element, then click *Edit > Save as new element*.
-
-[role="screenshot"]
-image::images/canvas_save_element.png[]
-
-To save a group of elements, press and hold Shift, select the elements you want to save, then click *Edit > Save as new element*.
-
-To access your saved elements, click *Add element > My elements*.
-
-[float]
-[[delete-elements]]
-==== Delete elements
-
-When you no longer need an element, delete it from your workpad.
-
-. Select the element you want to delete.
-
-. Click *Edit > Delete*.
-+
-[role="screenshot"]
-image::images/canvas_element_options.png[]
diff --git a/docs/canvas/canvas-present-workpad.asciidoc b/docs/canvas/canvas-present-workpad.asciidoc
index e0139ab943104..a6d801b74fce1 100644
--- a/docs/canvas/canvas-present-workpad.asciidoc
+++ b/docs/canvas/canvas-present-workpad.asciidoc
@@ -1,6 +1,6 @@
[role="xpack"]
[[canvas-present-workpad]]
-=== Present your workpad
+== Present your workpad
When you are ready to present your workpad, use and enable the presentation options.
@@ -21,3 +21,31 @@ image::images/canvas-autoplay-interval.png[Element autoplay interval]
image::images/canvas-fullscreen.png[Fullscreen mode]
. When you are ready to exit fullscreen mode, press the Esc (Escape) key.
+
+[float]
+[[zoom-in-out]]
+=== Use the zoom options
+
+To get a closer look at a portion of your workpad, use the zoom options.
+
+. Click *View > Zoom*.
+
+. Select the zoom option.
++
+[role="screenshot"]
+image::images/canvas-zoom-controls.png[Zoom controls]
+
+[float]
+[[configure-auto-refresh-interval]]
+=== Change the auto-refresh interval
+
+Change how often the data refreshes on your workpad.
+
+. Click *View > Auto refresh settings*.
+
+. Select the interval you want to use, or *Set a custom interval*.
++
+[role="screenshot"]
+image::images/canvas-refresh-interval.png[Element data refresh interval]
++
+To manually refresh the data, click image:canvas/images/canvas-refresh-data.png[].
diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc
index a095253c6cff3..f6cd2d93a9372 100644
--- a/docs/canvas/canvas-share-workpad.asciidoc
+++ b/docs/canvas/canvas-share-workpad.asciidoc
@@ -1,12 +1,12 @@
[role="xpack"]
[[workpad-share-options]]
-=== Share your workpad
+== Share your workpad
When you've finished your workpad, you can share it outside of {kib}.
[float]
[[export-single-workpad]]
-==== Export workpads
+=== Export workpads
Create a JSON file of your workpad that you can export outside of {kib}.
@@ -19,9 +19,9 @@ Want to export multiple workpads? Go to the *Canvas* home page, select the workp
[float]
[[create-workpad-pdf]]
-==== Create a PDF
+=== Create a PDF
-If you have a license that supports the {report-features}, you can create a PDF copy of your workpad that you can save and share outside {kib}.
+If you have a subscription that supports the {report-features}, you can create a PDF copy of your workpad that you can save and share outside {kib}.
Click *Share > PDF reports > Generate PDF*.
@@ -32,9 +32,9 @@ For more information, refer to <> or a script.
Click *Share > PDF reports > Copy POST URL*.
@@ -45,7 +45,7 @@ For more information, refer to <>.
+* Build presentations of your own live data with <>.
* Learn more about <> — the building blocks of your workpad.
diff --git a/docs/canvas/canvas-workpad.asciidoc b/docs/canvas/canvas-workpad.asciidoc
deleted file mode 100644
index 9d49c5f492bbb..0000000000000
--- a/docs/canvas/canvas-workpad.asciidoc
+++ /dev/null
@@ -1,140 +0,0 @@
-[role="xpack"]
-[[create-canvas-workpad]]
-== Create a workpad
-
-A Canvas _workpad_ provides you with a workspace where you can build presentations of your live data.
-
-To create a workpad, choose one of the following options:
-
-* <>
-
-* <>
-
-* <>
-
-* <>
-
-[float]
-[[blank-canvas-workpad]]
-=== Start with a blank workpad
-
-To use the background colors, images, and data of your choice, start with a blank workpad.
-
-. Open the menu, then go to *Canvas*.
-
-. On the *Canvas workpads* view, click *Create workpad*.
-
-. Add a *Name* to your workpad.
-
-. In the *Width* and *Height* fields, specify the size.
-
-. Select the layout.
-+
-For example, click *720p* for a traditional presentation layout.
-
-. Click the *Background color* picker, then select the background color for your workpad.
-+
-[role="screenshot"]
-image::images/canvas-background-color-picker.png[Canvas color picker]
-
-[float]
-[[canvas-template-workpad]]
-=== Create a workpad from a template
-
-If you're unsure about where to start, you can use one of the preconfigured templates that come with Canvas.
-
-. Open the menu, then go to *Canvas*.
-
-. On the *Canvas workpads* view, select *Templates*.
-
-. Click the preconfigured template that you want to use.
-
-. Add your own *Name* to the workpad.
-
-[float]
-[[import-canvas-workpad]]
-=== Import an existing workpad
-
-When you want to use a workpad that someone else has already started, import the JSON file into Canvas.
-
-. Open the menu, then go to *Canvas*.
-
-. On the *Canvas workpads* view, click and drag the file to the *Import workpad JSON file* field.
-
-[float]
-[[sample-data-workpad]]
-=== Use a sample data workpad
-
-Each of the sample data sets comes with a Canvas workpad that you can use for your own workpad inspiration.
-
-. Add a {kibana-ref}/add-sample-data.html[sample data set].
-
-. On the *Add Data* page, click *View data*, then select *Canvas*.
-
-[float]
-[[apply-workpad-styles]]
-=== Apply a set of styles to the entire workpad
-
-To make your workpad look exactly the way you want, use the editor to apply CSS overrides.
-
-. Expand *Global CSS overrides*.
-
-. Enter the CSS.
-+
-For example, to change the background on every page, enter:
-+
-[source,text]
---------------------------------------------------
-.canvasPage {
-background-color: #3990e6;
-}
---------------------------------------------------
-
-. Click *Apply stylesheet*.
-
-[float]
-[[configure-auto-refresh-interval]]
-=== Change the auto-refresh interval
-
-Change how often the data refreshes on your workpad.
-
-. Click *View > Auto refresh settings*.
-
-. Select the interval you want to use, or *Set a custom interval*.
-+
-[role="screenshot"]
-image::images/canvas-refresh-interval.png[Element data refresh interval]
-+
-To manually refresh the data, click image:canvas/images/canvas-refresh-data.png[].
-
-[float]
-[[zoom-in-out]]
-=== Use the zoom options
-
-To get a closer look at a portion of your workpad, use the zoom options.
-
-. Click *View > Zoom*.
-
-. Select the zoom option.
-+
-[role="screenshot"]
-image::images/canvas-zoom-controls.png[Zoom controls]
-
-[float]
-[[add-more-pages]]
-=== Add pages
-
-Organize your ideas onto separate pages by adding more pages.
-
-. Click *Page 1*, then click *+*.
-
-. On the *Page* editor panel, select the page transition from the *Transition* dropdown.
-+
-[role="screenshot"]
-image::images/canvas-add-pages.gif[Add pages]
-
-include::{kib-repo-dir}/canvas/canvas-elements.asciidoc[]
-
-include::{kib-repo-dir}/canvas/canvas-present-workpad.asciidoc[]
-
-include::{kib-repo-dir}/canvas/canvas-share-workpad.asciidoc[]
diff --git a/docs/canvas/images/clone_element.gif b/docs/canvas/images/clone_element.gif
new file mode 100644
index 0000000000000..ef8f44223d240
Binary files /dev/null and b/docs/canvas/images/clone_element.gif differ
diff --git a/docs/canvas/images/copy_variable_syntax.png b/docs/canvas/images/copy_variable_syntax.png
new file mode 100644
index 0000000000000..e583812cafb5c
Binary files /dev/null and b/docs/canvas/images/copy_variable_syntax.png differ
diff --git a/docs/canvas/images/specify_variable_syntax.png b/docs/canvas/images/specify_variable_syntax.png
new file mode 100644
index 0000000000000..98168c3d62ee5
Binary files /dev/null and b/docs/canvas/images/specify_variable_syntax.png differ
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 a03b1b74fc1ac..842f90b7047c8 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
@@ -81,6 +81,7 @@ readonly links: {
readonly loadingData: string;
readonly introduction: string;
};
+ readonly addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md
deleted file mode 100644
index 0451a2254dc40..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [abortController](./kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md)
-
-## SearchInterceptor.abortController property
-
-`abortController` used to signal all searches to abort.
-
-Signature:
-
-```typescript
-protected abortController: AbortController;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md
index db2c5d6957ad7..ef36b3f37b0c7 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md
@@ -2,12 +2,16 @@
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [getPendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md)
-## SearchInterceptor.getPendingCount$ property
+## SearchInterceptor.getPendingCount$() method
Returns an `Observable` over the current number of pending searches. This could mean that one of the search requests is still in flight, or that it has only received partial responses.
Signature:
```typescript
-getPendingCount$: () => Observable;
+getPendingCount$(): Observable;
```
+Returns:
+
+`Observable`
+
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md
deleted file mode 100644
index 59938a755a99e..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [hideToast](./kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md)
-
-## SearchInterceptor.hideToast property
-
-Signature:
-
-```typescript
-protected hideToast: () => void;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md
deleted file mode 100644
index 5799039de91bc..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [longRunningToast](./kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md)
-
-## SearchInterceptor.longRunningToast property
-
-The current long-running toast (if there is one).
-
-Signature:
-
-```typescript
-protected longRunningToast?: Toast;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md
index b3b7da05326d0..32954927504ae 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md
@@ -20,22 +20,15 @@ export declare class SearchInterceptor
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
-| [abortController](./kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md) | | AbortController | abortController used to signal all searches to abort. |
| [deps](./kibana-plugin-plugins-data-public.searchinterceptor.deps.md) | | SearchInterceptorDeps | |
-| [getPendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md) | | () => Observable<number> | Returns an Observable over the current number of pending searches. This could mean that one of the search requests is still in flight, or that it has only received partial responses. |
-| [hideToast](./kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md) | | () => void | |
-| [longRunningToast](./kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md) | | Toast | The current long-running toast (if there is one). |
-| [pendingCount](./kibana-plugin-plugins-data-public.searchinterceptor.pendingcount.md) | | number | The number of pending search requests. |
-| [pendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.pendingcount_.md) | | BehaviorSubject<number> | Observable that emits when the number of pending requests changes. |
| [requestTimeout](./kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md) | | number | undefined | |
-| [showToast](./kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md) | | () => void | |
-| [timeoutSubscriptions](./kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md) | | Subscription | The subscriptions from scheduling the automatic timeout for each request. |
## Methods
| Method | Modifiers | Description |
| --- | --- | --- |
+| [getPendingCount$()](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md) | | Returns an Observable over the current number of pending searches. This could mean that one of the search requests is still in flight, or that it has only received partial responses. |
| [runSearch(request, signal, strategy)](./kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md) | | |
-| [search(request, options)](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | Searches using the given search method. Overrides the AbortSignal with one that will abort either when cancelPending is called, when the request times out, or when the original AbortSignal is aborted. Updates the pendingCount when the request is started/finalized. |
+| [search(request, options)](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | Searches using the given search method. Overrides the AbortSignal with one that will abort either when cancelPending is called, when the request times out, or when the original AbortSignal is aborted. Updates pendingCount$ when the request is started/finalized. |
| [setupTimers(options)](./kibana-plugin-plugins-data-public.searchinterceptor.setuptimers.md) | | |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.pendingcount.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.pendingcount.md
deleted file mode 100644
index 7dd2bd3e6703f..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.pendingcount.md
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [pendingCount](./kibana-plugin-plugins-data-public.searchinterceptor.pendingcount.md)
-
-## SearchInterceptor.pendingCount property
-
-The number of pending search requests.
-
-Signature:
-
-```typescript
-protected pendingCount: number;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.pendingcount_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.pendingcount_.md
deleted file mode 100644
index dad0fca0bfe08..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.pendingcount_.md
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [pendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.pendingcount_.md)
-
-## SearchInterceptor.pendingCount$ property
-
-Observable that emits when the number of pending requests changes.
-
-Signature:
-
-```typescript
-protected pendingCount$: BehaviorSubject;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md
index 38ddda7b4e184..1752d183a8737 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md
@@ -4,7 +4,7 @@
## SearchInterceptor.search() method
-Searches using the given `search` method. Overrides the `AbortSignal` with one that will abort either when `cancelPending` is called, when the request times out, or when the original `AbortSignal` is aborted. Updates the `pendingCount` when the request is started/finalized.
+Searches using the given `search` method. Overrides the `AbortSignal` with one that will abort either when `cancelPending` is called, when the request times out, or when the original `AbortSignal` is aborted. Updates `pendingCount$` when the request is started/finalized.
Signature:
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md
deleted file mode 100644
index e495c72b57215..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [showToast](./kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md)
-
-## SearchInterceptor.showToast property
-
-Signature:
-
-```typescript
-protected showToast: () => void;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md
deleted file mode 100644
index 12f200e037784..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [timeoutSubscriptions](./kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md)
-
-## SearchInterceptor.timeoutSubscriptions property
-
-The subscriptions from scheduling the automatic timeout for each request.
-
-Signature:
-
-```typescript
-protected timeoutSubscriptions: Subscription;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.http.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.http.md
index 1146179c13d63..66c31bb6fcf80 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.http.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.http.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-http: CoreStart['http'];
+http: CoreSetup['http'];
```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.md
index 1291af5359887..63eb67ce48246 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.md
@@ -14,9 +14,9 @@ export interface SearchInterceptorDeps
| Property | Type | Description |
| --- | --- | --- |
-| [application](./kibana-plugin-plugins-data-public.searchinterceptordeps.application.md) | ApplicationStart | |
-| [http](./kibana-plugin-plugins-data-public.searchinterceptordeps.http.md) | CoreStart['http'] | |
-| [toasts](./kibana-plugin-plugins-data-public.searchinterceptordeps.toasts.md) | ToastsStart | |
-| [uiSettings](./kibana-plugin-plugins-data-public.searchinterceptordeps.uisettings.md) | CoreStart['uiSettings'] | |
+| [http](./kibana-plugin-plugins-data-public.searchinterceptordeps.http.md) | CoreSetup['http'] | |
+| [startServices](./kibana-plugin-plugins-data-public.searchinterceptordeps.startservices.md) | Promise<[CoreStart, any, unknown]> | |
+| [toasts](./kibana-plugin-plugins-data-public.searchinterceptordeps.toasts.md) | ToastsSetup | |
+| [uiSettings](./kibana-plugin-plugins-data-public.searchinterceptordeps.uisettings.md) | CoreSetup['uiSettings'] | |
| [usageCollector](./kibana-plugin-plugins-data-public.searchinterceptordeps.usagecollector.md) | SearchUsageCollector | |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.application.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.startservices.md
similarity index 61%
rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.application.md
rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.startservices.md
index a8cd1b170a595..855d0652058b8 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.application.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.startservices.md
@@ -1,11 +1,11 @@
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) > [application](./kibana-plugin-plugins-data-public.searchinterceptordeps.application.md)
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) > [startServices](./kibana-plugin-plugins-data-public.searchinterceptordeps.startservices.md)
-## SearchInterceptorDeps.application property
+## SearchInterceptorDeps.startServices property
Signature:
```typescript
-application: ApplicationStart;
+startServices: Promise<[CoreStart, any, unknown]>;
```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.toasts.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.toasts.md
index 0023b34af10c3..1f560dfa5cf7c 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.toasts.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.toasts.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-toasts: ToastsStart;
+toasts: ToastsSetup;
```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.uisettings.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.uisettings.md
index 425e177ec9300..a34d223c34ac2 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.uisettings.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.uisettings.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-uiSettings: CoreStart['uiSettings'];
+uiSettings: CoreSetup['uiSettings'];
```
diff --git a/docs/glossary.asciidoc b/docs/glossary.asciidoc
index 51470513198b9..1edb33032418b 100644
--- a/docs/glossary.asciidoc
+++ b/docs/glossary.asciidoc
@@ -416,5 +416,5 @@ See
// tag::workpad-def[]
A workspace where you build presentations of your live data in <>.
See
-{kibana-ref}/create-canvas-workpad.html[Create a workpad].
+{kibana-ref}/canvas.html[Create a workpad].
// end::workpad-def[]
diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc
index bffb3f97cd1b9..f750784c47043 100644
--- a/docs/setup/connect-to-elasticsearch.asciidoc
+++ b/docs/setup/connect-to-elasticsearch.asciidoc
@@ -97,7 +97,7 @@ Using a wildcard is the more popular approach.
comparisons.
+
Kibana reads the index mapping and lists all fields that contain a timestamp. If your
-index doesn't have time-based data, choose *I don't want to use the Time Filter*.
+index doesn't have time-based data, choose *I don't want to use the time filter*.
+
You must select a time field to use global time filters on your dashboards.
diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc
index 355684f7448a1..317ec67dd7c0a 100644
--- a/docs/user/canvas.asciidoc
+++ b/docs/user/canvas.asciidoc
@@ -18,16 +18,170 @@ With Canvas, you can:
* Focus the data you want to display with filters.
[role="screenshot"]
-image::images/canvas-gs-example.png[]
+image::images/canvas-gs-example.png[Getting started example]
For a quick overview of Canvas, watch link:https://www.youtube.com/watch?v=ZqvF_5-1xjQ[Stand out with Canvas].
-//When https://github.com/elastic/Video/issues/358 is resolved, update this link.
+
+[float]
+[[create-workpads]]
+== Create workpads
+
+A _workpad_ provides you with a space where you can build presentations of your live data.
+
+[float]
+[[start-with-a-blank-workpad]]
+=== Start with a blank workpad
+
+To use the background colors, images, and data of your choice, start with a blank workpad.
+
+. Open the menu, then go to *Canvas*.
+
+. On the *Canvas workpads* view, click *Create workpad*.
+
+. Add a *Name* to your workpad.
+
+. In the *Width* and *Height* fields, specify the size.
+
+. Select the layout.
++
+For example, click *720p* for a traditional presentation layout.
+
+. Click the *Background color* picker, then select the background color for your workpad.
++
+[role="screenshot"]
+image::images/canvas-background-color-picker.png[Canvas color picker]
+
+[float]
+[[create-workpads-from-templates]]
+=== Create workpads from templates
+
+If you're unsure about where to start, you can use one of the preconfigured templates that come with Canvas.
+
+. Open the menu, then go to *Canvas*.
+
+. On the *Canvas workpads* view, select *Templates*.
+
+. Click the preconfigured template that you want to use.
+
+. Add your own *Name* to the workpad.
+
+[float]
+[[import-existing-workpads]]
+=== Import existing workpads
+
+When you want to use a workpad that someone else has already started, import the JSON file into Canvas.
+
+. Open the menu, then go to *Canvas*.
+
+. On the *Canvas workpads* view, click and drag the file to the *Import workpad JSON file* field.
+
+[float]
+[[use-sample-data-workpads]]
+=== Use sample data workpads
+
+Each of the sample data sets comes with a Canvas workpad that you can use for your own workpad inspiration.
+
+. Add a {kibana-ref}/add-sample-data.html[sample data set].
+
+. On the *Add Data* page, click *View data*, then select *Canvas*.
+
+[float]
+[[add-canvas-elements]]
+== Add elements
+
+Create a story about your data by adding elements to your workpad that include images, text, charts, and more.
+
+[float]
+[[create-elements]]
+=== Create elements
+
+Choose the type of element you want to use, then use the preconfigured demo data to familiarize yourself with the element. When you're ready, connect the element to your own data. By default, most of the elements you create use
+demo data until you change the data source. The demo data includes a small data set that you can use to experiment with your element.
+
+To begin, click *Add element*, then select the element you want to use.
+
+[role="screenshot"]
+image::images/canvas-element-select.gif[Canvas elements]
+
+When you're ready to connect the element to your data, select *Data*, then select one of the following data sources:
+
+* *{es} SQL* — Access your data in {es} using {ref}/sql-spec.html[SQL syntax].
+
+* *{es} documents* — Access your data in {es} without using aggregations. To use, select an index and fields, and optionally enter a query using the <>. Use the *{es} documents* data source when you have low volume datasets, to view raw documents, or to plot exact, non-aggregated values on a chart.
+
+* *Timelion* — Access your time series data using <> queries. To use Timelion queries, you can enter a query using the <>.
+
+Each element can display a different data source, and pages and workpads often contain multiple data sources.
+
+When you're ready to save your element, select the element, then click *Edit > Save as new element*.
+
+[role="screenshot"]
+image::images/canvas_save_element.png[]
+
+To save a group of elements, press and hold Shift, select the elements you want to save, then click *Edit > Save as new element*.
+
+Elements are saved in *Add element > My elements*.
+
+[float]
+[[add-existing-visuualizations]]
+=== Add existing visualizations
+
+Add <> to your workpad, such as maps and visualizations.
+
+. Click *Add element > Add from Visualize Library*.
+
+. Select the saved object you want to add.
++
+[role="screenshot"]
+image::images/canvas-map-embed.gif[]
+
+. To use the customization options, click the panel menu, then select one of the following options:
+
+* *Edit map* — Opens <> or <> so that you can edit the original saved object.
+
+* *Edit panel title* — Adds a title to the saved object.
+
+* *Customize time range* — Exposes a time filter dedicated to the saved object.
+
+* *Inspect* — Allows you to drill down into the element data.
+
+[float]
+[[add-your-own-images]]
+=== Add your own images
+
+To personalize your workpad, add your own logos and graphics.
+
+. Click *Add element > Manage assets*.
+
+. On the *Manage workpad assets* window, drag and drop your images.
+
+. To add the image to the workpad, click the *Create image element* icon.
++
+[role="screenshot"]
+image::images/canvas-add-image.gif[Add image to Canvas]
+
+[float]
+[[add-more-pages]]
+== Add pages
+
+Organize and separate your ideas by adding more pages.
+
+. Click *Page 1*, then click *+*.
+
+. On the *Page* editor panel, select the page transition from the *Transition* dropdown.
++
+[role="screenshot"]
+image::images/canvas-add-pages.gif[Add pages]
--
-include::{kib-repo-dir}/canvas/canvas-tutorial.asciidoc[]
+include::{kib-repo-dir}/canvas/canvas-edit-workpads.asciidoc[]
+
+include::{kib-repo-dir}/canvas/canvas-present-workpad.asciidoc[]
-include::{kib-repo-dir}/canvas/canvas-workpad.asciidoc[]
+include::{kib-repo-dir}/canvas/canvas-share-workpad.asciidoc[]
+
+include::{kib-repo-dir}/canvas/canvas-tutorial.asciidoc[]
include::{kib-repo-dir}/canvas/canvas-expression-lifecycle.asciidoc[]
diff --git a/examples/embeddable_examples/kibana.json b/examples/embeddable_examples/kibana.json
index 771c19cfdbd3d..0ac40ae1889de 100644
--- a/examples/embeddable_examples/kibana.json
+++ b/examples/embeddable_examples/kibana.json
@@ -4,7 +4,7 @@
"kibanaVersion": "kibana",
"server": true,
"ui": true,
- "requiredPlugins": ["embeddable", "uiActions"],
+ "requiredPlugins": ["embeddable", "uiActions", "dashboard"],
"optionalPlugins": [],
"extraPublicDirs": ["public/todo", "public/hello_world", "public/todo/todo_ref_embeddable"],
"requiredBundles": ["kibanaReact"]
diff --git a/examples/embeddable_examples/public/book/add_book_to_library_action.tsx b/examples/embeddable_examples/public/book/add_book_to_library_action.tsx
new file mode 100644
index 0000000000000..b74a1d5642982
--- /dev/null
+++ b/examples/embeddable_examples/public/book/add_book_to_library_action.tsx
@@ -0,0 +1,55 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { createAction, IncompatibleActionError } from '../../../../src/plugins/ui_actions/public';
+import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
+import { ViewMode, isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public';
+
+interface ActionContext {
+ embeddable: BookEmbeddable;
+}
+
+export const ACTION_ADD_BOOK_TO_LIBRARY = 'ACTION_ADD_BOOK_TO_LIBRARY';
+
+export const createAddBookToLibraryAction = () =>
+ createAction({
+ getDisplayName: () =>
+ i18n.translate('embeddableExamples.book.addToLibrary', {
+ defaultMessage: 'Add Book To Library',
+ }),
+ type: ACTION_ADD_BOOK_TO_LIBRARY,
+ order: 100,
+ getIconType: () => 'folderCheck',
+ isCompatible: async ({ embeddable }: ActionContext) => {
+ return (
+ embeddable.type === BOOK_EMBEDDABLE &&
+ embeddable.getInput().viewMode === ViewMode.EDIT &&
+ isReferenceOrValueEmbeddable(embeddable) &&
+ !embeddable.inputIsRefType(embeddable.getInput())
+ );
+ },
+ execute: async ({ embeddable }: ActionContext) => {
+ if (!isReferenceOrValueEmbeddable(embeddable)) {
+ throw new IncompatibleActionError();
+ }
+ const newInput = await embeddable.getInputAsRefType();
+ embeddable.updateInput(newInput);
+ },
+ });
diff --git a/examples/embeddable_examples/public/book/book_component.tsx b/examples/embeddable_examples/public/book/book_component.tsx
index 064e13c131a0a..e46487641b913 100644
--- a/examples/embeddable_examples/public/book/book_component.tsx
+++ b/examples/embeddable_examples/public/book/book_component.tsx
@@ -20,7 +20,7 @@ import React from 'react';
import { EuiFlexItem, EuiFlexGroup, EuiIcon } from '@elastic/eui';
import { EuiText } from '@elastic/eui';
-import { EuiFlexGrid } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { withEmbeddableSubscription } from '../../../../src/plugins/embeddable/public';
import { BookEmbeddableInput, BookEmbeddableOutput, BookEmbeddable } from './book_embeddable';
@@ -44,26 +44,32 @@ function wrapSearchTerms(task?: string, search?: string) {
);
}
-export function BookEmbeddableComponentInner({ input: { search }, output: { attributes } }: Props) {
+export function BookEmbeddableComponentInner({
+ input: { search },
+ output: { attributes },
+ embeddable,
+}: Props) {
const title = attributes?.title;
const author = attributes?.author;
const readIt = attributes?.readIt;
+ const byReference = embeddable.inputIsRefType(embeddable.getInput());
+
return (
-
+
{title ? (
-
+
{createButton}
@@ -195,7 +245,7 @@ export const IndexPatternTable = ({ canSave, history }: Props) => {
sorting={sorting}
search={search}
/>
-
+
);
};
diff --git a/src/plugins/index_pattern_management/public/index.ts b/src/plugins/index_pattern_management/public/index.ts
index 2d6db13757eea..9a0fd39fb4fd9 100644
--- a/src/plugins/index_pattern_management/public/index.ts
+++ b/src/plugins/index_pattern_management/public/index.ts
@@ -41,3 +41,5 @@ export {
IndexPatternCreationOption,
IndexPatternListConfig,
} from './service';
+
+export { MlCardState } from './types';
diff --git a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx
index bcabd55c87975..add45a07e0c5f 100644
--- a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx
+++ b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx
@@ -34,7 +34,7 @@ import {
CreateIndexPatternWizardWithRouter,
} from '../components';
import { IndexPatternManagementStartDependencies, IndexPatternManagementStart } from '../plugin';
-import { IndexPatternManagmentContext } from '../types';
+import { IndexPatternManagmentContext, MlCardState } from '../types';
const readOnlyBadge = {
text: i18n.translate('indexPatternManagement.indexPatterns.badge.readOnly.text', {
@@ -48,7 +48,8 @@ const readOnlyBadge = {
export async function mountManagementSection(
getStartServices: StartServicesAccessor,
- params: ManagementAppMountParams
+ params: ManagementAppMountParams,
+ getMlCardState: () => MlCardState
) {
const [
{ chrome, application, savedObjects, uiSettings, notifications, overlays, http, docLinks },
@@ -73,6 +74,7 @@ export async function mountManagementSection(
data,
indexPatternManagementStart: indexPatternManagementStart as IndexPatternManagementStart,
setBreadcrumbs: params.setBreadcrumbs,
+ getMlCardState,
};
ReactDOM.render(
diff --git a/src/plugins/index_pattern_management/public/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts
index ec8100db42085..6a9ef23e3732e 100644
--- a/src/plugins/index_pattern_management/public/mocks.ts
+++ b/src/plugins/index_pattern_management/public/mocks.ts
@@ -39,6 +39,9 @@ const createSetupContract = (): IndexPatternManagementSetup => ({
getAll: jest.fn(),
getById: jest.fn(),
} as any,
+ environment: {
+ update: jest.fn(),
+ },
});
const createStartContract = (): IndexPatternManagementStart => ({
diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts
index fe680eff8657e..ee1e00fcafd98 100644
--- a/src/plugins/index_pattern_management/public/plugin.ts
+++ b/src/plugins/index_pattern_management/public/plugin.ts
@@ -86,7 +86,9 @@ export class IndexPatternManagementPlugin
mount: async (params) => {
const { mountManagementSection } = await import('./management_app');
- return mountManagementSection(core.getStartServices, params);
+ return mountManagementSection(core.getStartServices, params, () =>
+ this.indexPatternManagementService.environmentService.getEnvironment().ml()
+ );
},
});
diff --git a/src/plugins/index_pattern_management/public/service/environment/environment.mock.ts b/src/plugins/index_pattern_management/public/service/environment/environment.mock.ts
new file mode 100644
index 0000000000000..2c2c68b8ead2d
--- /dev/null
+++ b/src/plugins/index_pattern_management/public/service/environment/environment.mock.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 { EnvironmentService, EnvironmentServiceSetup } from './environment';
+import { MlCardState } from '../../types';
+
+const createSetupMock = (): jest.Mocked => {
+ const setup = {
+ update: jest.fn(),
+ };
+ return setup;
+};
+
+const createMock = (): jest.Mocked> => {
+ const service = {
+ setup: jest.fn(),
+ getEnvironment: jest.fn(() => ({
+ ml: () => MlCardState.ENABLED,
+ })),
+ };
+ service.setup.mockImplementation(createSetupMock);
+ return service;
+};
+
+export const environmentServiceMock = {
+ createSetup: createSetupMock,
+ create: createMock,
+};
diff --git a/src/plugins/index_pattern_management/public/service/environment/environment.test.ts b/src/plugins/index_pattern_management/public/service/environment/environment.test.ts
new file mode 100644
index 0000000000000..1aa67ba751b81
--- /dev/null
+++ b/src/plugins/index_pattern_management/public/service/environment/environment.test.ts
@@ -0,0 +1,49 @@
+/*
+ * 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 { EnvironmentService } from './environment';
+import { MlCardState } from '../../types';
+
+describe('EnvironmentService', () => {
+ describe('setup', () => {
+ test('allows multiple update calls', () => {
+ const setup = new EnvironmentService().setup();
+ expect(() => {
+ setup.update({ ml: () => MlCardState.ENABLED });
+ }).not.toThrow();
+ });
+ });
+
+ describe('getEnvironment', () => {
+ test('returns default values', () => {
+ const service = new EnvironmentService();
+ expect(service.getEnvironment().ml()).toEqual(MlCardState.DISABLED);
+ });
+
+ test('returns last state of update calls', () => {
+ let cardState = MlCardState.DISABLED;
+ const service = new EnvironmentService();
+ const setup = service.setup();
+ setup.update({ ml: () => cardState });
+ expect(service.getEnvironment().ml()).toEqual(MlCardState.DISABLED);
+ cardState = MlCardState.ENABLED;
+ expect(service.getEnvironment().ml()).toEqual(MlCardState.ENABLED);
+ });
+ });
+});
diff --git a/src/plugins/index_pattern_management/public/service/environment/environment.ts b/src/plugins/index_pattern_management/public/service/environment/environment.ts
new file mode 100644
index 0000000000000..f40ce3589fa76
--- /dev/null
+++ b/src/plugins/index_pattern_management/public/service/environment/environment.ts
@@ -0,0 +1,52 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { MlCardState } from '../../types';
+
+/** @public */
+export interface Environment {
+ /**
+ * Flag whether ml features should be advertised
+ */
+ readonly ml: () => MlCardState;
+}
+
+export class EnvironmentService {
+ private environment = {
+ ml: () => MlCardState.DISABLED,
+ };
+
+ public setup() {
+ return {
+ /**
+ * Update the environment to influence how available features are presented.
+ * @param update
+ */
+ update: (update: Partial) => {
+ this.environment = Object.assign({}, this.environment, update);
+ },
+ };
+ }
+
+ public getEnvironment() {
+ return this.environment;
+ }
+}
+
+export type EnvironmentServiceSetup = ReturnType;
diff --git a/src/plugins/index_pattern_management/public/service/environment/index.ts b/src/plugins/index_pattern_management/public/service/environment/index.ts
new file mode 100644
index 0000000000000..91d14c358e7db
--- /dev/null
+++ b/src/plugins/index_pattern_management/public/service/environment/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 { EnvironmentService, Environment, EnvironmentServiceSetup } from './environment';
diff --git a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts
index d4cc9c95e76a7..06b9b83b1b601 100644
--- a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts
+++ b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts
@@ -21,6 +21,7 @@ import { HttpSetup } from '../../../../core/public';
import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation';
import { IndexPatternListManager, IndexPatternListConfig } from './list';
import { FieldFormatEditors } from './field_format_editors';
+import { EnvironmentService } from './environment';
import {
BytesFormatEditor,
@@ -49,11 +50,13 @@ export class IndexPatternManagementService {
indexPatternCreationManager: IndexPatternCreationManager;
indexPatternListConfig: IndexPatternListManager;
fieldFormatEditors: FieldFormatEditors;
+ environmentService: EnvironmentService;
constructor() {
this.indexPatternCreationManager = new IndexPatternCreationManager();
this.indexPatternListConfig = new IndexPatternListManager();
this.fieldFormatEditors = new FieldFormatEditors();
+ this.environmentService = new EnvironmentService();
}
public setup({ httpClient }: SetupDependencies) {
@@ -83,6 +86,7 @@ export class IndexPatternManagementService {
creation: creationManagerSetup,
list: indexPatternListConfigSetup,
fieldFormatEditors: fieldFormatEditorsSetup,
+ environment: this.environmentService.setup(),
};
}
diff --git a/src/plugins/index_pattern_management/public/types.ts b/src/plugins/index_pattern_management/public/types.ts
index 97941687e652d..2876bd6227350 100644
--- a/src/plugins/index_pattern_management/public/types.ts
+++ b/src/plugins/index_pattern_management/public/types.ts
@@ -44,8 +44,15 @@ export interface IndexPatternManagmentContext {
data: DataPublicPluginStart;
indexPatternManagementStart: IndexPatternManagementStart;
setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs'];
+ getMlCardState: () => MlCardState;
}
export type IndexPatternManagmentContextValue = KibanaReactContextValue<
IndexPatternManagmentContext
>;
+
+export enum MlCardState {
+ HIDDEN,
+ DISABLED,
+ ENABLED,
+}
diff --git a/src/plugins/vis_type_vega/public/components/vega_actions_menu.tsx b/src/plugins/vis_type_vega/public/components/vega_actions_menu.tsx
index f10954df432c2..33fa1ceefd3d5 100644
--- a/src/plugins/vis_type_vega/public/components/vega_actions_menu.tsx
+++ b/src/plugins/vis_type_vega/public/components/vega_actions_menu.tsx
@@ -70,7 +70,7 @@ function VegaActionsMenu({ formatHJson, formatJson }: VegaActionsMenuProps) {
return (
{
{(copy) => (
-
+
{copyToClipboardLabel}
)}
diff --git a/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx b/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx
index 3b9427c96e62a..6dfa7a23c4fe8 100644
--- a/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx
+++ b/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx
@@ -47,11 +47,13 @@ export const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => {
id: 'data-viewer--id',
name: dataSetsLabel,
content: ,
+ 'data-test-subj': 'vegaDataInspectorDataViewerButton',
},
{
id: 'signal-viewer--id',
name: signalValuesLabel,
content: ,
+ 'data-test-subj': 'vegaDataInspectorSignalViewerButton',
},
{
id: 'spec-viewer--id',
@@ -59,6 +61,7 @@ export const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => {
content: (
),
+ 'data-test-subj': 'vegaDataInspectorSpecViewerButton',
},
];
diff --git a/test/functional/apps/management/_index_patterns_empty.ts b/test/functional/apps/management/_index_patterns_empty.ts
new file mode 100644
index 0000000000000..4ae2e7836ac37
--- /dev/null
+++ b/test/functional/apps/management/_index_patterns_empty.ts
@@ -0,0 +1,66 @@
+/*
+ * 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 { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function ({ getPageObjects, getService }: FtrProviderContext) {
+ const esArchiver = getService('esArchiver');
+ const kibanaServer = getService('kibanaServer');
+ const PageObjects = getPageObjects(['common', 'settings']);
+ const testSubjects = getService('testSubjects');
+ const globalNav = getService('globalNav');
+ const es = getService('legacyEs');
+
+ describe('index pattern empty view', () => {
+ before(async () => {
+ await esArchiver.load('empty_kibana');
+ await kibanaServer.uiSettings.replace({});
+ await PageObjects.settings.navigateTo();
+ });
+
+ after(async () => {
+ await esArchiver.unload('empty_kibana');
+ await esArchiver.loadIfNeeded('makelogs');
+ });
+
+ // create index pattern and return to verify list
+ it(`shows empty views`, async () => {
+ // @ts-expect-error
+ await es.transport.request({
+ path: '/_all',
+ method: 'DELETE',
+ });
+ await PageObjects.settings.clickKibanaIndexPatterns();
+ await testSubjects.existOrFail('createAnyway');
+ // @ts-expect-error
+ await es.transport.request({
+ path: '/logstash-a/_doc',
+ method: 'POST',
+ body: { user: 'matt', message: 20 },
+ });
+ await testSubjects.click('refreshIndicesButton');
+ await testSubjects.existOrFail('createIndexPatternButton', { timeout: 5000 });
+ await PageObjects.settings.createIndexPattern('logstash-*', '');
+ });
+
+ it(`doesn't show read-only badge`, async () => {
+ await globalNav.badgeMissingOrFail();
+ });
+ });
+}
diff --git a/test/functional/apps/management/_kibana_settings.js b/test/functional/apps/management/_kibana_settings.js
index 2a488a94c6889..e2b20bacc0b39 100644
--- a/test/functional/apps/management/_kibana_settings.js
+++ b/test/functional/apps/management/_kibana_settings.js
@@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }) {
before(async function () {
// delete .kibana index and then wait for Kibana to re-create it
await kibanaServer.uiSettings.replace({});
- await PageObjects.settings.createIndexPattern();
+ await PageObjects.settings.createIndexPattern('logstash-*');
await PageObjects.settings.navigateTo();
});
diff --git a/test/functional/apps/management/index.js b/test/functional/apps/management/index.js
index 97e7314f9678e..d5f0c286af7a5 100644
--- a/test/functional/apps/management/index.js
+++ b/test/functional/apps/management/index.js
@@ -43,6 +43,7 @@ export default function ({ getService, loadTestFile }) {
loadTestFile(require.resolve('./_scripted_fields'));
loadTestFile(require.resolve('./_scripted_fields_preview'));
loadTestFile(require.resolve('./_mgmt_import_saved_objects'));
+ loadTestFile(require.resolve('./_index_patterns_empty'));
});
describe('', function () {
diff --git a/test/functional/apps/visualize/_vega_chart.ts b/test/functional/apps/visualize/_vega_chart.ts
index a1ed8460f1b22..b59d9590bb62a 100644
--- a/test/functional/apps/visualize/_vega_chart.ts
+++ b/test/functional/apps/visualize/_vega_chart.ts
@@ -16,10 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
-
+import { unzip } from 'lodash';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
+const getTestSpec = (expression: string) => `
+{
+config: { "kibana": {"renderer": "svg"} }
+$schema: https://vega.github.io/schema/vega/v5.json
+marks: [{
+ type: text
+ encode: { update: { text: { value: "Test" } } }
+}]
+signals: [ {
+ on: [{
+ events: click
+ update: ${expression}
+ }]
+}]}`;
+
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const PageObjects = getPageObjects([
'timePicker',
@@ -29,7 +44,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
'vegaChart',
]);
const filterBar = getService('filterBar');
+ const inspector = getService('inspector');
+ const vegaDebugInspectorView = getService('vegaDebugInspector');
const log = getService('log');
+ const retry = getService('retry');
+ const browser = getService('browser');
describe('vega chart in visualize app', () => {
before(async () => {
@@ -88,5 +107,177 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
});
});
+
+ describe('Inspector Panel', () => {
+ it('should have inspector enabled', async () => {
+ await inspector.expectIsEnabled();
+ });
+
+ describe('Request Tab', () => {
+ beforeEach(async () => {
+ await inspector.open();
+ });
+
+ afterEach(async () => {
+ await inspector.close();
+ });
+
+ it('should contain "Statistics", "Request", "Response" tabs', async () => {
+ await inspector.openInspectorRequestsView();
+
+ for (const getFn of [
+ inspector.getOpenRequestDetailRequestButton,
+ inspector.getOpenRequestDetailResponseButton,
+ inspector.getOpenRequestStatisticButton,
+ ]) {
+ await retry.try(async () => {
+ const requestStatisticTab = await getFn();
+
+ expect(await requestStatisticTab.isEnabled()).to.be(true);
+ });
+ }
+ });
+
+ it('should set the default query name if not given in the schema', async () => {
+ const requests = await inspector.getRequestNames();
+
+ expect(requests).to.be('Unnamed request #0');
+ });
+
+ it('should log the request statistic', async () => {
+ await inspector.openInspectorRequestsView();
+ const rawTableData = await inspector.getTableData();
+
+ expect(unzip(rawTableData)[0].join(', ')).to.be(
+ 'Hits, Hits (total), Query time, Request timestamp'
+ );
+ });
+ });
+
+ describe('Debug Tab', () => {
+ beforeEach(async () => {
+ await inspector.open();
+ });
+
+ afterEach(async () => {
+ await inspector.close();
+ });
+
+ it('should contain "Data Sets", "Signal Values", "Spec" tabs', async () => {
+ await vegaDebugInspectorView.openVegaDebugInspectorView();
+
+ for (const getFn of [
+ vegaDebugInspectorView.getOpenDataViewerButton,
+ vegaDebugInspectorView.getOpenSignalViewerButton,
+ vegaDebugInspectorView.getOpenSpecViewerButton,
+ ]) {
+ await retry.try(async () => {
+ const requestStatisticTab = await getFn();
+
+ expect(await requestStatisticTab.isEnabled()).to.be(true);
+ });
+ }
+ });
+
+ it('should contain data on "Signal Values" tab', async () => {
+ await vegaDebugInspectorView.openVegaDebugInspectorView();
+ await vegaDebugInspectorView.navigateToSignalViewerTab();
+
+ const { rows, columns } = await vegaDebugInspectorView.getGridTableData();
+
+ expect(columns.join(', ')).to.be('Signal, Value');
+ expect(rows.length).to.be.greaterThan(0);
+ expect(rows[0].length).to.be(2);
+ });
+
+ it('should contain data on "Signal Values" tab', async () => {
+ await vegaDebugInspectorView.openVegaDebugInspectorView();
+ await vegaDebugInspectorView.navigateToDataViewerTab();
+
+ const { rows, columns } = await vegaDebugInspectorView.getGridTableData();
+
+ expect(columns.length).to.be.greaterThan(0);
+ expect(rows.length).to.be.greaterThan(0);
+ });
+
+ it('should be able to copy vega spec to clipboard', async () => {
+ await vegaDebugInspectorView.openVegaDebugInspectorView();
+ await vegaDebugInspectorView.navigateToSpecViewerTab();
+
+ const copyCopyToClipboardButton = await vegaDebugInspectorView.getCopyClipboardButton();
+
+ expect(await copyCopyToClipboardButton.isEnabled()).to.be(true);
+
+ // The "clipboard-read" permission of the Permissions API must be granted
+ if (!(await browser.checkBrowserPermission('clipboard-read'))) {
+ return;
+ }
+
+ await copyCopyToClipboardButton.click();
+
+ expect(
+ (await browser.getClipboardValue()).includes(
+ '"$schema": "https://vega.github.io/schema/vega-lite/'
+ )
+ ).to.be(true);
+ });
+ });
+ });
+
+ describe('Vega extension functions', () => {
+ beforeEach(async () => {
+ await filterBar.removeAllFilters();
+ });
+
+ const fillSpecAndGo = async (newSpec: string) => {
+ await PageObjects.vegaChart.fillSpec(newSpec);
+ await PageObjects.visEditor.clickGo();
+
+ const viewContainer = await PageObjects.vegaChart.getViewContainer();
+ const textElement = await viewContainer.findByTagName('text');
+
+ await textElement.click();
+ };
+
+ it('should update global time range by calling "kibanaSetTimeFilter" expression', async () => {
+ await fillSpecAndGo(getTestSpec('kibanaSetTimeFilter("2019", "2020")'));
+
+ const currentTimeRange = await PageObjects.timePicker.getTimeConfig();
+
+ expect(currentTimeRange.start).to.be('Jan 1, 2019 @ 00:00:00.000');
+ expect(currentTimeRange.end).to.be('Jan 1, 2020 @ 00:00:00.000');
+ });
+
+ it('should set filter by calling "kibanaAddFilter" expression', async () => {
+ await fillSpecAndGo(
+ getTestSpec('kibanaAddFilter({ query_string: { query: "response:200" }})')
+ );
+
+ expect(await filterBar.getFilterCount()).to.be(1);
+ });
+
+ it('should remove filter by calling "kibanaRemoveFilter" expression', async () => {
+ await filterBar.addFilter('response', 'is', '200');
+
+ expect(await filterBar.getFilterCount()).to.be(1);
+
+ await fillSpecAndGo(
+ getTestSpec('kibanaRemoveFilter({ match_phrase: { response: "200" }})')
+ );
+
+ expect(await filterBar.getFilterCount()).to.be(0);
+ });
+
+ it('should remove all filters by calling "kibanaRemoveAllFilters" expression', async () => {
+ await filterBar.addFilter('response', 'is', '200');
+ await filterBar.addFilter('response', 'is', '500');
+
+ expect(await filterBar.getFilterCount()).to.be(2);
+
+ await fillSpecAndGo(getTestSpec('kibanaRemoveAllFilters()'));
+
+ expect(await filterBar.getFilterCount()).to.be(0);
+ });
+ });
});
}
diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts
index 4b80647c8749d..a4285a5f94d51 100644
--- a/test/functional/page_objects/settings_page.ts
+++ b/test/functional/page_objects/settings_page.ts
@@ -55,15 +55,6 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
await testSubjects.click('indexPatterns');
await PageObjects.header.waitUntilLoadingHasFinished();
-
- // check for the index pattern info flyout that covers the
- // create index pattern button on smaller screens
- // @ts-ignore
- await retry.waitFor('index pattern info flyout', async () => {
- if (await testSubjects.exists('CreateIndexPatternPrompt')) {
- await testSubjects.click('CreateIndexPatternPrompt > euiFlyoutCloseButton');
- } else return true;
- });
}
async getAdvancedSettings(propertyName: string) {
@@ -311,9 +302,7 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
}
async isIndexPatternListEmpty() {
- await testSubjects.existOrFail('indexPatternTable', { timeout: 5000 });
- const indexPatternList = await this.getIndexPatternList();
- return indexPatternList.length === 0;
+ return !(await testSubjects.exists('indexPatternTable', { timeout: 5000 }));
}
async removeLogstashIndexPatternIfExist() {
diff --git a/test/functional/page_objects/vega_chart_page.ts b/test/functional/page_objects/vega_chart_page.ts
index b9906911b00f1..1173c35af3384 100644
--- a/test/functional/page_objects/vega_chart_page.ts
+++ b/test/functional/page_objects/vega_chart_page.ts
@@ -18,8 +18,14 @@
*/
import { Key } from 'selenium-webdriver';
+import expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
+const compareSpecs = (first: string, second: string) => {
+ const normalizeSpec = (spec: string) => spec.replace(/[\n ]/g, '');
+ return normalizeSpec(first) === normalizeSpec(second);
+};
+
export function VegaChartPageProvider({
getService,
getPageObjects,
@@ -28,24 +34,57 @@ export function VegaChartPageProvider({
const testSubjects = getService('testSubjects');
const browser = getService('browser');
const { common } = getPageObjects(['common']);
+ const retry = getService('retry');
class VegaChartPage {
- public async getSpec() {
+ public getEditor() {
+ return testSubjects.find('vega-editor');
+ }
+
+ public getViewContainer() {
+ return find.byCssSelector('div.vgaVis__view');
+ }
+
+ public getControlContainer() {
+ return find.byCssSelector('div.vgaVis__controls');
+ }
+
+ public async getRawSpec() {
// Adapted from console_page.js:getVisibleTextFromAceEditor(). Is there a common utilities file?
- const editor = await testSubjects.find('vega-editor');
+ const editor = await this.getEditor();
const lines = await editor.findAllByClassName('ace_line_group');
- const linesText = await Promise.all(
+
+ return await Promise.all(
lines.map(async (line) => {
return await line.getVisibleText();
})
);
- return linesText.join('\n');
}
- public async typeInSpec(text: string) {
- const editor = await testSubjects.find('vega-editor');
+ public async getSpec() {
+ return (await this.getRawSpec()).join('\n');
+ }
+
+ public async focusEditor() {
+ const editor = await this.getEditor();
const textarea = await editor.findByClassName('ace_content');
+
await textarea.click();
+ }
+
+ public async fillSpec(newSpec: string) {
+ await retry.try(async () => {
+ await this.cleanSpec();
+ await this.focusEditor();
+ await browser.pressKeys(newSpec);
+
+ expect(compareSpecs(await this.getSpec(), newSpec)).to.be(true);
+ });
+ }
+
+ public async typeInSpec(text: string) {
+ await this.focusEditor();
+
let repeats = 20;
while (--repeats > 0) {
await browser.pressKeys(Key.ARROW_UP);
@@ -55,12 +94,16 @@ export function VegaChartPageProvider({
await browser.pressKeys(text);
}
- public async getViewContainer() {
- return await find.byCssSelector('div.vgaVis__view');
- }
+ public async cleanSpec() {
+ const editor = await this.getEditor();
+ const aceGutter = await editor.findByClassName('ace_gutter');
+
+ await retry.try(async () => {
+ await aceGutter.doubleClick();
+ await browser.pressKeys(Key.BACK_SPACE);
- public async getControlContainer() {
- return await find.byCssSelector('div.vgaVis__controls');
+ expect(await this.getSpec()).to.be('');
+ });
}
public async getYAxisLabels() {
diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts
index b0eec5e24f635..e81845023a8fa 100644
--- a/test/functional/services/common/browser.ts
+++ b/test/functional/services/common/browser.ts
@@ -489,5 +489,17 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
const _id = idOrElement instanceof WebElementWrapper ? idOrElement._webElement : idOrElement;
await driver.switchTo().frame(_id);
}
+
+ public async checkBrowserPermission(permission: string): Promise {
+ const result: any = await driver.executeAsyncScript(
+ `navigator.permissions.query({name:'${permission}'}).then(arguments[0])`
+ );
+
+ return Boolean(result?.state === 'granted');
+ }
+
+ public getClipboardValue(): Promise {
+ return driver.executeAsyncScript('navigator.clipboard.readText().then(arguments[0])');
+ }
})();
}
diff --git a/test/functional/services/dashboard/panel_actions.ts b/test/functional/services/dashboard/panel_actions.ts
index 0f5d6ea74a6b6..bc21a62b9df79 100644
--- a/test/functional/services/dashboard/panel_actions.ts
+++ b/test/functional/services/dashboard/panel_actions.ts
@@ -70,6 +70,17 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }: Ft
await PageObjects.common.waitForTopNavToBeVisible();
}
+ async editPanelByTitle(title?: string) {
+ log.debug(`editPanelByTitle(${title})`);
+ if (title) {
+ const panelOptions = await this.getPanelHeading(title);
+ await this.openContextMenu(panelOptions);
+ } else {
+ await this.openContextMenu();
+ }
+ await testSubjects.clickWhenNotDisabled(EDIT_PANEL_DATA_TEST_SUBJ);
+ }
+
async clickExpandPanelToggle() {
await testSubjects.click(TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ);
}
diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts
new file mode 100644
index 0000000000000..40157caab5756
--- /dev/null
+++ b/test/functional/services/data_grid.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 { FtrProviderContext } from '../ftr_provider_context';
+
+interface TabbedGridData {
+ columns: string[];
+ rows: string[][];
+}
+
+export function DataGridProvider({ getService }: FtrProviderContext) {
+ const find = getService('find');
+
+ class DataGrid {
+ async getDataGridTableData(): Promise {
+ const table = await find.byCssSelector('.euiDataGrid');
+ const $ = await table.parseDomContent();
+
+ const columns = $('.euiDataGridHeaderCell__content')
+ .toArray()
+ .map((cell) => $(cell).text());
+ const rows = $.findTestSubjects('dataGridRow')
+ .toArray()
+ .map((row) =>
+ $(row)
+ .find('.euiDataGridRowCell__truncate')
+ .toArray()
+ .map((cell) => $(cell).text())
+ );
+
+ return {
+ columns,
+ rows,
+ };
+ }
+ }
+
+ return new DataGrid();
+}
diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts
index 7891a6b00f729..4c97d672bae2e 100644
--- a/test/functional/services/index.ts
+++ b/test/functional/services/index.ts
@@ -47,7 +47,12 @@ import { RemoteProvider } from './remote';
import { RenderableProvider } from './renderable';
import { TableProvider } from './table';
import { ToastsProvider } from './toasts';
-import { PieChartProvider, ElasticChartProvider } from './visualizations';
+import { DataGridProvider } from './data_grid';
+import {
+ PieChartProvider,
+ ElasticChartProvider,
+ VegaDebugInspectorViewProvider,
+} from './visualizations';
import { ListingTableProvider } from './listing_table';
import { SavedQueryManagementComponentProvider } from './saved_query_management_component';
import { KibanaSupertestProvider } from './supertest';
@@ -72,12 +77,14 @@ export const services = {
dashboardPanelActions: DashboardPanelActionsProvider,
flyout: FlyoutProvider,
comboBox: ComboBoxProvider,
+ dataGrid: DataGridProvider,
embedding: EmbeddingProvider,
renderable: RenderableProvider,
table: TableProvider,
browser: BrowserProvider,
pieChart: PieChartProvider,
inspector: InspectorProvider,
+ vegaDebugInspector: VegaDebugInspectorViewProvider,
appsMenu: AppsMenuProvider,
globalNav: GlobalNavProvider,
toasts: ToastsProvider,
diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts
index d8ac224ddd9bc..1c0bf7ad46df1 100644
--- a/test/functional/services/inspector.ts
+++ b/test/functional/services/inspector.ts
@@ -233,6 +233,18 @@ export function InspectorProvider({ getService }: FtrProviderContext) {
const singleRequest = await testSubjects.find('inspectorRequestName');
return await singleRequest.getVisibleText();
}
+
+ public getOpenRequestStatisticButton() {
+ return testSubjects.find('inspectorRequestDetailStatistics');
+ }
+
+ public getOpenRequestDetailRequestButton() {
+ return testSubjects.find('inspectorRequestDetailRequest');
+ }
+
+ public getOpenRequestDetailResponseButton() {
+ return testSubjects.find('inspectorRequestDetailResponse');
+ }
}
return new Inspector();
diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts
index 09fede7fe2546..d51b32f3cc497 100644
--- a/test/functional/services/remote/webdriver.ts
+++ b/test/functional/services/remote/webdriver.ts
@@ -53,9 +53,15 @@ const SECOND = 1000;
const MINUTE = 60 * SECOND;
const NO_QUEUE_COMMANDS = ['getLog', 'getStatus', 'newSession', 'quit'];
const downloadDir = resolve(REPO_ROOT, 'target/functional-tests/downloads');
-const chromiumDownloadPrefs = {
+const chromiumUserPrefs = {
'download.default_directory': downloadDir,
'download.prompt_for_download': false,
+ 'profile.content_settings.exceptions.clipboard': {
+ '[*.],*': {
+ last_modified: Date.now(),
+ setting: 1,
+ },
+ },
};
/**
@@ -135,7 +141,7 @@ async function attemptToCreateCommand(
const prefs = new logging.Preferences();
prefs.setLevel(logging.Type.BROWSER, logging.Level.ALL);
- chromeOptions.setUserPreferences(chromiumDownloadPrefs);
+ chromeOptions.setUserPreferences(chromiumUserPrefs);
chromeOptions.setLoggingPrefs(prefs);
chromeOptions.set('unexpectedAlertBehaviour', 'accept');
chromeOptions.setAcceptInsecureCerts(config.acceptInsecureCerts);
@@ -185,7 +191,7 @@ async function attemptToCreateCommand(
edgeOptions.setBinaryPath(edgePaths.browserPath);
const options = edgeOptions.get('ms:edgeOptions');
// overriding options to include preferences
- Object.assign(options, { prefs: chromiumDownloadPrefs });
+ Object.assign(options, { prefs: chromiumUserPrefs });
edgeOptions.set('ms:edgeOptions', options);
const session = await new Builder()
.forBrowser('MicrosoftEdge')
diff --git a/test/functional/services/visualizations/index.ts b/test/functional/services/visualizations/index.ts
index 1da1691b07c2a..10019e63d684e 100644
--- a/test/functional/services/visualizations/index.ts
+++ b/test/functional/services/visualizations/index.ts
@@ -19,3 +19,4 @@
export { PieChartProvider } from './pie_chart';
export { ElasticChartProvider } from './elastic_chart';
+export { VegaDebugInspectorViewProvider } from './vega_debug_inspector';
diff --git a/test/functional/services/visualizations/vega_debug_inspector.ts b/test/functional/services/visualizations/vega_debug_inspector.ts
new file mode 100644
index 0000000000000..3847ebbbf1279
--- /dev/null
+++ b/test/functional/services/visualizations/vega_debug_inspector.ts
@@ -0,0 +1,68 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export function VegaDebugInspectorViewProvider({ getService }: FtrProviderContext) {
+ const testSubjects = getService('testSubjects');
+ const inspector = getService('inspector');
+ const dataGrid = getService('dataGrid');
+
+ class VegaDebugInspectorView {
+ async openVegaDebugInspectorView() {
+ await inspector.openInspectorView('inspectorViewChooserVega debug');
+ }
+
+ public getOpenDataViewerButton() {
+ return testSubjects.find('vegaDataInspectorDataViewerButton');
+ }
+
+ public getOpenSignalViewerButton() {
+ return testSubjects.find('vegaDataInspectorSignalViewerButton');
+ }
+
+ public getOpenSpecViewerButton() {
+ return testSubjects.find('vegaDataInspectorSpecViewerButton');
+ }
+
+ public getCopyClipboardButton() {
+ return testSubjects.find('vegaDataInspectorCopyClipboardButton');
+ }
+
+ public getGridTableData() {
+ return dataGrid.getDataGridTableData();
+ }
+
+ public async navigateToDataViewerTab() {
+ const dataViewerButton = await this.getOpenDataViewerButton();
+ await dataViewerButton.click();
+ }
+
+ public async navigateToSignalViewerTab() {
+ const signalViewerButton = await this.getOpenSignalViewerButton();
+ await signalViewerButton.click();
+ }
+
+ public async navigateToSpecViewerTab() {
+ const specViewerButton = await this.getOpenSpecViewerButton();
+ await specViewerButton.click();
+ }
+ }
+
+ return new VegaDebugInspectorView();
+}
diff --git a/x-pack/package.json b/x-pack/package.json
index b426e790c2d47..2b52646e0f748 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -255,8 +255,8 @@
"cronstrue": "^1.51.0",
"cytoscape": "^3.10.0",
"d3": "3.5.17",
- "d3-scale": "1.0.7",
"d3-array": "1.2.4",
+ "d3-scale": "1.0.7",
"dedent": "^0.7.0",
"del": "^5.1.0",
"dragselect": "1.13.1",
@@ -267,7 +267,7 @@
"font-awesome": "4.7.0",
"formsy-react": "^1.1.5",
"fp-ts": "^2.3.1",
- "get-port": "4.2.0",
+ "get-port": "^4.2.0",
"getos": "^3.1.0",
"git-url-parse": "11.1.2",
"github-markdown-css": "^2.10.0",
diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts
index bdf3f6a0acf90..7f6e3feac0671 100644
--- a/x-pack/plugins/data_enhanced/public/plugin.ts
+++ b/x-pack/plugins/data_enhanced/public/plugin.ts
@@ -31,20 +31,26 @@ export class DataEnhancedPlugin
KUERY_LANGUAGE_NAME,
setupKqlQuerySuggestionProvider(core)
);
- }
- public start(core: CoreStart, plugins: DataEnhancedStartDependencies) {
- setAutocompleteService(plugins.data.autocomplete);
const enhancedSearchInterceptor = new EnhancedSearchInterceptor(
{
toasts: core.notifications.toasts,
- application: core.application,
http: core.http,
uiSettings: core.uiSettings,
- usageCollector: plugins.data.search.usageCollector,
+ startServices: core.getStartServices(),
+ usageCollector: data.search.usageCollector,
},
core.injectedMetadata.getInjectedVar('esRequestTimeout') as number
);
- plugins.data.search.setInterceptor(enhancedSearchInterceptor);
+
+ data.__enhance({
+ search: {
+ searchInterceptor: enhancedSearchInterceptor,
+ },
+ });
+ }
+
+ public start(core: CoreStart, plugins: DataEnhancedStartDependencies) {
+ setAutocompleteService(plugins.data.autocomplete);
}
}
diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts
index d004511fa4674..fe954f1602cd3 100644
--- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts
+++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts
@@ -6,7 +6,7 @@
import { coreMock } from '../../../../../src/core/public/mocks';
import { EnhancedSearchInterceptor } from './search_interceptor';
-import { CoreStart } from 'kibana/public';
+import { CoreSetup, CoreStart } from 'kibana/public';
import { AbortError } from '../../../../../src/plugins/data/common';
const timeTravel = (msToRun = 0) => {
@@ -19,13 +19,14 @@ const error = jest.fn();
const complete = jest.fn();
let searchInterceptor: EnhancedSearchInterceptor;
+let mockCoreSetup: MockedKeys;
let mockCoreStart: MockedKeys;
jest.useFakeTimers();
function mockFetchImplementation(responses: any[]) {
let i = 0;
- mockCoreStart.http.fetch.mockImplementation(() => {
+ mockCoreSetup.http.fetch.mockImplementation(() => {
const { time = 0, value = {}, isError = false } = responses[i++];
return new Promise((resolve, reject) =>
setTimeout(() => {
@@ -39,6 +40,7 @@ describe('EnhancedSearchInterceptor', () => {
let mockUsageCollector: any;
beforeEach(() => {
+ mockCoreSetup = coreMock.createSetup();
mockCoreStart = coreMock.createStart();
next.mockClear();
@@ -54,12 +56,20 @@ describe('EnhancedSearchInterceptor', () => {
trackLongQueryRunBeyondTimeout: jest.fn(),
};
+ const mockPromise = new Promise((resolve) => {
+ resolve([
+ {
+ application: mockCoreStart.application,
+ },
+ ]);
+ });
+
searchInterceptor = new EnhancedSearchInterceptor(
{
- toasts: mockCoreStart.notifications.toasts,
- application: mockCoreStart.application,
- http: mockCoreStart.http,
- uiSettings: mockCoreStart.uiSettings,
+ toasts: mockCoreSetup.notifications.toasts,
+ startServices: mockPromise as any,
+ http: mockCoreSetup.http,
+ uiSettings: mockCoreSetup.uiSettings,
usageCollector: mockUsageCollector,
},
1000
@@ -229,8 +239,8 @@ describe('EnhancedSearchInterceptor', () => {
expect(error).toHaveBeenCalled();
expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError);
- expect(mockCoreStart.http.fetch).toHaveBeenCalledTimes(2);
- expect(mockCoreStart.http.delete).toHaveBeenCalled();
+ expect(mockCoreSetup.http.fetch).toHaveBeenCalledTimes(2);
+ expect(mockCoreSetup.http.delete).toHaveBeenCalled();
});
test('should not DELETE a running async search on async timeout prior to first response', async () => {
@@ -253,8 +263,8 @@ describe('EnhancedSearchInterceptor', () => {
expect(error).toHaveBeenCalled();
expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError);
- expect(mockCoreStart.http.fetch).toHaveBeenCalled();
- expect(mockCoreStart.http.delete).not.toHaveBeenCalled();
+ expect(mockCoreSetup.http.fetch).toHaveBeenCalled();
+ expect(mockCoreSetup.http.delete).not.toHaveBeenCalled();
});
test('should DELETE a running async search on async timeout after first response', async () => {
@@ -285,16 +295,16 @@ describe('EnhancedSearchInterceptor', () => {
expect(next).toHaveBeenCalled();
expect(error).not.toHaveBeenCalled();
- expect(mockCoreStart.http.fetch).toHaveBeenCalled();
- expect(mockCoreStart.http.delete).not.toHaveBeenCalled();
+ expect(mockCoreSetup.http.fetch).toHaveBeenCalled();
+ expect(mockCoreSetup.http.delete).not.toHaveBeenCalled();
// Long enough to reach the timeout but not long enough to reach the next response
await timeTravel(1000);
expect(error).toHaveBeenCalled();
expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError);
- expect(mockCoreStart.http.fetch).toHaveBeenCalledTimes(2);
- expect(mockCoreStart.http.delete).toHaveBeenCalled();
+ expect(mockCoreSetup.http.fetch).toHaveBeenCalledTimes(2);
+ expect(mockCoreSetup.http.delete).toHaveBeenCalled();
});
test('should DELETE a running async search on async timeout on error from fetch', async () => {
@@ -327,16 +337,16 @@ describe('EnhancedSearchInterceptor', () => {
expect(next).toHaveBeenCalled();
expect(error).not.toHaveBeenCalled();
- expect(mockCoreStart.http.fetch).toHaveBeenCalled();
- expect(mockCoreStart.http.delete).not.toHaveBeenCalled();
+ expect(mockCoreSetup.http.fetch).toHaveBeenCalled();
+ expect(mockCoreSetup.http.delete).not.toHaveBeenCalled();
// Long enough to reach the timeout but not long enough to reach the next response
await timeTravel(10);
expect(error).toHaveBeenCalled();
expect(error.mock.calls[0][0]).toBe(responses[1].value);
- expect(mockCoreStart.http.fetch).toHaveBeenCalledTimes(2);
- expect(mockCoreStart.http.delete).toHaveBeenCalled();
+ expect(mockCoreSetup.http.fetch).toHaveBeenCalledTimes(2);
+ expect(mockCoreSetup.http.delete).toHaveBeenCalled();
});
});
@@ -367,7 +377,7 @@ describe('EnhancedSearchInterceptor', () => {
await timeTravel();
- const areAllRequestsAborted = mockCoreStart.http.fetch.mock.calls.every(
+ const areAllRequestsAborted = mockCoreSetup.http.fetch.mock.calls.every(
([{ signal }]) => signal?.aborted
);
expect(areAllRequestsAborted).toBe(true);
diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts
index bff9e2cb9048c..ae6dddf33536f 100644
--- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts
+++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts
@@ -20,8 +20,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor {
/**
* This class should be instantiated with a `requestTimeout` corresponding with how many ms after
* requests are initiated that they should automatically cancel.
- * @param toasts The `core.notifications.toasts` service
- * @param application The `core.application` service
+ * @param deps `SearchInterceptorDeps`
* @param requestTimeout Usually config value `elasticsearch.requestTimeout`
*/
constructor(deps: SearchInterceptorDeps, requestTimeout?: number) {
@@ -78,7 +77,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor {
const { combinedSignal, cleanup } = this.setupTimers(options);
const aborted$ = from(toPromise(combinedSignal));
- this.pendingCount$.next(++this.pendingCount);
+ this.pendingCount$.next(this.pendingCount$.getValue() + 1);
return this.runSearch(request, combinedSignal, options?.strategy).pipe(
expand((response) => {
@@ -113,7 +112,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor {
},
}),
finalize(() => {
- this.pendingCount$.next(--this.pendingCount);
+ this.pendingCount$.next(this.pendingCount$.getValue() - 1);
cleanup();
})
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts
index 6f82946c0ea14..e999d40a3f8e6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { mockHistory } from './react_router_history.mock';
+export { mockHistory, mockLocation } from './react_router_history.mock';
export { mockKibanaContext } from './kibana_context.mock';
export { mockLicenseContext } from './license_context.mock';
export {
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts
index fd422465d87f1..779eb1a043e8c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts
@@ -5,7 +5,7 @@
*/
/**
- * NOTE: This variable name MUST start with 'mock*' in order for
+ * NOTE: These variable names MUST start with 'mock*' in order for
* Jest to accept its use within a jest.mock()
*/
export const mockHistory = {
@@ -15,9 +15,17 @@ export const mockHistory = {
pathname: '/current-path',
},
};
+export const mockLocation = {
+ key: 'someKey',
+ pathname: '/current-path',
+ search: '?query=something',
+ hash: '#hash',
+ state: {},
+};
jest.mock('react-router-dom', () => ({
useHistory: jest.fn(() => mockHistory),
+ useLocation: jest.fn(() => mockLocation),
}));
/**
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx
index d6c38629d8143..4d2b790e7fb97 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx
@@ -5,7 +5,7 @@
*/
import React, { useContext } from 'react';
-import { EuiPage, EuiPageBody, EuiPageContent, EuiEmptyPrompt, EuiButton } from '@elastic/eui';
+import { EuiPageContent, EuiEmptyPrompt, EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { sendTelemetry } from '../../../shared/telemetry';
@@ -32,43 +32,40 @@ export const EmptyState: React.FC = () => {
};
return (
-
+ <>
-
-
-
-
-
-
-
- }
- titleSize="l"
- body={
-