diff --git a/Jenkinsfile b/Jenkinsfile index 11dca544f32261..b6a36c79f877dd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -41,6 +41,7 @@ kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true) { 'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9), 'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10), 'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'), + 'xpack-pageLoadMetrics': kibanaPipeline.functionalTestProcess('xpack-pageLoadMetrics', './test/scripts/jenkins_xpack_page_load_metrics.sh'), 'xpack-securitySolutionCypress': { processNumber -> whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/']) { kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')(processNumber) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 052afc91f543f9..defc573dd2a042 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,7 +10,7 @@ This section summarizes the changes in each release. -*<> +* <> * <> * <> * <> @@ -42,6 +42,15 @@ This section summarizes the changes in each release. See <>. +[float] +[[security-update-7.1.1]] +=== Security update +In {kib} 5.4.0 and later, TSVB visualizations contain a stored XSS flaw. Attackers that can +edit and create TSVB visualizations can obtain sensitive information, or perform +destructive actions, on behalf of the {kib} users who edit the TSVB visualization, CVE-2020-7015. + +You must upgrade to 7.7.1. If you are unable to upgrade, set `metrics.enabled:false` in your kibana.yml file to disable TSVB. + [float] [[bug-7.7.1]] === Bug fixes @@ -164,7 +173,7 @@ Canvas:: * Improves expression autocomplete {pull}52035[#52035] Dashboard:: * Use Elasticsearch `_async_search` instead of `_search` when it is available (excluding TSVB, Timelion, and Vega) {pull}59224[#59224} -* When queries run more than 10 seconds, show a pop-up to allow users to run the queries beyond the configured timeout or cancel the queries {pull}60706[#60706] +* When queries run more than 10 seconds, show a pop-up to allow users to run the queries beyond the configured Elasticsearch query timeout or cancel the queries {pull}60706[#60706] * Dashboard/add panel flow {pull}59918[#59918] * Moves the "Create New" button in add panel flyout to the top to make it more visible to the user {pull}56428[#56428] Lens and visualizations:: diff --git a/docs/apm/advanced-queries.asciidoc b/docs/apm/advanced-queries.asciidoc index f89f994e59e574..7b771eb662616e 100644 --- a/docs/apm/advanced-queries.asciidoc +++ b/docs/apm/advanced-queries.asciidoc @@ -11,7 +11,7 @@ or, to only show transactions that are slower than a specified time threshold. ==== Example APM app queries * Exclude response times slower than 2000 ms: `transaction.duration.us > 2000000` -* Filter by response status code: `context.response.status_code >= 400` +* Filter by response status code: `context.response.status_code ≥ 400` * Filter by single user ID: `context.user.id : 12` When querying in the APM app, you're merely searching and selecting data from fields in Elasticsearch documents. diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc index 3a6a96fca9d097..db2f85c54c7624 100644 --- a/docs/apm/service-maps.asciidoc +++ b/docs/apm/service-maps.asciidoc @@ -62,9 +62,9 @@ Machine learning jobs can be created to calculate anomaly scores on APM transact When these jobs are active, service maps will display a color-coded anomaly indicator based on the detected anomaly score: [horizontal] -image:apm/images/green-service.png[APM green service]:: Max anomaly score **<=25**. Service is healthy. +image:apm/images/green-service.png[APM green service]:: Max anomaly score **≤25**. Service is healthy. image:apm/images/yellow-service.png[APM yellow service]:: Max anomaly score **26-74**. Anomalous activity detected. Service may be degraded. -image:apm/images/red-service.png[APM red service]:: Max anomaly score **>=75**. Anomalous activity detected. Service is unhealthy. +image:apm/images/red-service.png[APM red service]:: Max anomaly score **≥75**. Anomalous activity detected. Service is unhealthy. [role="screenshot"] image::apm/images/apm-service-map-anomaly.png[Example view of anomaly scores on service maps in the APM app] @@ -92,10 +92,10 @@ Type and subtype are based on `span.type`, and `span.subtype`. Service maps are supported for the following Agent versions: [horizontal] -Go Agent:: >= v1.7.0 -Java Agent:: >= v1.13.0 -.NET Agent:: >= v1.3.0 -Node.js Agent:: >= v3.6.0 -Python Agent:: >= v5.5.0 -Ruby Agent:: >= v3.6.0 -Real User Monitoring (RUM) Agent:: >= v4.7.0 +Go Agent:: ≥ v1.7.0 +Java Agent:: ≥ v1.13.0 +.NET Agent:: ≥ v1.3.0 +Node.js Agent:: ≥ v3.6.0 +Python Agent:: ≥ v5.5.0 +Ruby Agent:: ≥ v3.6.0 +Real User Monitoring (RUM) Agent:: ≥ v4.7.0 diff --git a/docs/canvas/canvas-elements.asciidoc b/docs/canvas/canvas-elements.asciidoc index 4149039a3f87b1..9c7467bb452fd6 100644 --- a/docs/canvas/canvas-elements.asciidoc +++ b/docs/canvas/canvas-elements.asciidoc @@ -27,28 +27,30 @@ By default, most of the elements you create use demo data until you change the d * *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 a <>, then customize it to fit your display needs. +Add <> to your workpad, such as maps and visualizations. -. Click *Embed object*. +. Click *Add element > Add from Visualize Library*. -. Select the object you want to add. +. 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 <> so that you can edit the original map. +* *Edit map* — Opens <> or <> so that you can edit the original saved object. -* *Customize panel* — Specifies the object title options. +* *Edit panel title* — Adds a title to the saved object. -* *Inspect* — Allows you to drill down into the element data. +* *Customize time range* — Exposes a time filter dedicated to the saved object. -* *Customize time range* — Exposes a time filter dedicated to the map. +* *Inspect* — Allows you to drill down into the element data. [float] [[canvas-add-image]] @@ -56,7 +58,7 @@ image::images/canvas-map-embed.gif[] To personalize your workpad, add your own logos and graphics. -. Click *Manage assets*. +. Click *Add element > Manage assets*. . On the *Manage workpad assets* window, drag and drop your images. @@ -83,40 +85,25 @@ Move and resize your elements to meet your design needs. [[format-canvas-elements]] ==== Format elements -Align, distribute, and reorder elements for consistency and readability across your workpad pages. - -Access the align, distribute, and reorder options by clicking the *Element options* icon. - -[role="screenshot"] -image::images/canvas_element_options.png[] +For consistency and readability across your workpad pages, align, distribute, and reorder elements. -To align elements: +To align two or more elements: . Press and hold Shift, then select the elements you want to align. -. Click the , then select *Group*. +. Click *Edit > Alignment*, then select the alignment option. -. Click the *Element options* icon, then select *Alignment*. - -. Select the alignment option. - -To distribute elements: +To distribute three or more elements: . Press and hold Shift, then select the elements you want to distribute. -. Click the *Element options* icon, then select *Group*. - -. Click the *Element options* icon, then select *Distribution*. - -. Select the distribution option. +. Click *Edit > Distribution*, then select the distribution option. To reorder elements: . Select the element you want to reorder. -. Click the *Element options* icon, then select *Order*. - -. Select the order option. +. Click *Edit > Order*, then select the order option. [float] [[data-display]] @@ -157,14 +144,14 @@ text.align: center; To use the elements across all workpads, save the elements. -When you're ready to save your element, select the element, then click the *Save as new element* icon. +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, then select the elements you want to save. +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*, then select *My elements*. +To access your saved elements, click *Add element > My elements*. [float] [[delete-elements]] @@ -174,9 +161,7 @@ When you no longer need an element, delete it from your workpad. . Select the element you want to delete. -. Click the *Element options* icon. +. Click *Edit > Delete*. + [role="screenshot"] image::images/canvas_element_options.png[] - -. Select *Delete*. diff --git a/docs/canvas/canvas-present-workpad.asciidoc b/docs/canvas/canvas-present-workpad.asciidoc index 9cd4ecc9519e15..e0139ab9431041 100644 --- a/docs/canvas/canvas-present-workpad.asciidoc +++ b/docs/canvas/canvas-present-workpad.asciidoc @@ -4,24 +4,20 @@ When you are ready to present your workpad, use and enable the presentation options. -[float] -[[view-fullscreen-mode]] -==== View your workpad in fullscreen mode +. Configure the autoplay options. -Click the *Enter fullscreen mode* icon. +.. From the workpad menu, click *View > Autoplay settings*. +.. Under *Change cycling interval*, select the interval you want to use, or *Set a custom interval*. ++ [role="screenshot"] -image::images/canvas-fullscreen.png[Fullscreen mode] - -[float] -[[enable-autoplay]] -==== Enable autoplay +image::images/canvas-autoplay-interval.png[Element autoplay interval] -Automatically cycle through your workpads pages in fullscreen mode. +. To enable autoplay, click *View > Turn autoplay on*. -. Click the *Control settings* icon. - -. Under *Change cycling interval*, select the interval you want to use. +. To start your presentation, click *View > Enter fullscreen mode*. + [role="screenshot"] -image::images/canvas-refresh-interval.png[Element data refresh interval] +image::images/canvas-fullscreen.png[Fullscreen mode] + +. When you are ready to exit fullscreen mode, press the Esc (Escape) key. diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index 5cae3fcc7b5319..a095253c6cff36 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -10,14 +10,12 @@ When you've finished your workpad, you can share it outside of {kib}. Create a JSON file of your workpad that you can export outside of {kib}. -. From your workpad, click the *Share workpad* icon. +Click *Share > Download as JSON*. -. Select *Download as JSON*. -+ [role="screenshot"] image::images/canvas-export-workpad.png[Export single workpad] -Want to export multiple workpads? Go to the *Canvas workpads* view, select the workpads you want to export, then click *Export*. +Want to export multiple workpads? Go to the *Canvas* home page, select the workpads you want to export, then click *Export*. [float] [[create-workpad-pdf]] @@ -25,69 +23,43 @@ Want to export multiple workpads? Go to the *Canvas workpads* view, select the w 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}. -For more information, refer to <>. - -. From your workpad, click the *Share workpad* icon, then select *PDF reports*. +Click *Share > PDF reports > Generate PDF*. -. Click *Generate PDF*. -+ [role="screenshot"] image::images/canvas-generate-pdf.gif[Generate PDF] +For more information, refer to <>. + [float] [[create-workpad-URL]] ==== Create a POST URL If you have a license that supports the {report-features}, you can create a POST URL that you can use to automatically generate PDF reports using Watcher or a script. -For more information, refer to <>. - -. From your workpad, click the *Share workpad* icon, then select *PDF reports*. +Click *Share > PDF reports > Copy POST URL*. -. Click *Copy POST URL*. -+ [role="screenshot"] image::images/canvas-create-URL.gif[Create POST URL] +For more information, refer to <>. + [float] [[add-workpad-website]] ==== Share the workpad on a website beta[] Canvas allows you to create _shareables_, which are workpads that you download and securely share on any website. To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. -. From your workpad, click the *Share this workpad* icon, then select *Share on a website*. +. Click *Share > Share on a website*. -. On the *Share on a website* pane, follow the instructions. +. Follow the *Share on a website* instructions. . To customize the workpad behavior to autoplay the pages or hide the toolbar, use the inline parameters. + To make sure that your data remains secure, the data in the JSON file is not connected to {kib}. Canvas does not display elements that manipulate the data on the workpad. + [role="screenshot"] -image::images/canvas-embed_workpad.gif[Share the workpad on a website] +image::canvas/images/canvas-embed_workpad.gif[Share the workpad on a website] + NOTE: Shareable workpads encode the current state of the workpad in a JSON file. When you make changes to the workpad, the changes do not appear in the shareable workpad on your website. -[float] -[[change-the-workpad-settings]] -==== Change the settings - -After you've added the workpad to your website, you can change the autoplay and toolbar settings. - -To change the autoplay settings: - -. Click the settings icon. - -. Click *Auto Play*, then change the settings. -+ -[role="screenshot"] -image::images/canvas_share_autoplay_480.gif[Autoplay settings] - -To change the toolbar settings: - -. Click the settings icon. - -. Click *Toolbar*, then change the settings. -+ -[role="screenshot"] -image::images/canvas_share_hidetoolbar_480.gif[Hide toolbar settings] +. To change the settings, click the settings icon, then choose the settings you want to use. diff --git a/docs/canvas/canvas-tutorial.asciidoc b/docs/canvas/canvas-tutorial.asciidoc index a38ab4a69598e0..9b23817de2767f 100644 --- a/docs/canvas/canvas-tutorial.asciidoc +++ b/docs/canvas/canvas-tutorial.asciidoc @@ -10,76 +10,64 @@ To get up and running with Canvas, use the following tutorial where you'll creat For this tutorial, you'll need to add the <>. [float] -=== Create and personalize your workpad +=== Create your workpad Your first step to working with Canvas is to create a workpad. -. Open *Canvas*. +. Open the menu, then click *Kibana > Canvas*. -. Click *Create workpad*. - -. To add a *Name* for your workpad, use the editor. For example, `My Canvas Workpad`. +. On the *Canvas workpads* page, click *Create workpad*. [float] === Customize your workpad with images To customize your workpad to look the way you want, add your own images. -. Click *Add element*, then click *Image*. +. Click *Add element > Image > Image*. + -The default Elastic logo image appears on your page. +The default Elastic logo image appears on the page. . To replace the Elastic logo with your own image, select the image, then use the editor. -. To move the image, click and drag it to your preferred location. - [role="screenshot"] image::images/canvas-image-element.png[] -You'll notice that the image is tagged as an asset, which allows you to reuse the image from *Manage assets*. - [float] === Customize your data with metrics Customize your data by connecting it to the Sample eCommerce orders data. -. Click *Add element*, then click *Metric*. +. Click *Add element > Chart > Metric*. + -By default, the *Metric* element is connected to a demo data source, which enables you to experiment with the element before you connect it to your own data source. - -. To connect the element to your own data source, make sure that the element is selected, then click *Data*. +By default, the element is connected to the demo data, which enables you to experiment with the element before you connect it to your own data source. -.. Click *Change your data source*, then click *Elasticsearch SQL*. +. To connect the element to your own data source, make sure that the element is selected, click *Data > Demo data > Elasticsearch SQL*. -.. In the *Elasticsearch SQL query* field, enter the following query: +.. In the *Query* field, enter the following: + `SELECT sum(taxless_total_price) AS sum_total_price FROM "kibana_sample_data_ecommerce"` -+ -The query selects the total price field and sets it to the sum_total_price field. These fields are pulled from the kibana_sample_data_ecommerce index that you installed. -.. To verify that the data is correct, click *Preview*. If you like what you see, click *Save*. +.. Click *Save*. + -At this point, the element displays an error. +The query selects the total price field and sets it to the sum_total_price field. All fields are pulled from the kibana_sample_data_ecommerce index. -. Specify how to process and display the data. +. At this point, the element appears as an error, so you need to change the element display options. .. Click *Display* -.. Under *Number*, select *Value* from the function drop-down list, then select *sum_total_price* from the column drop-down list. +.. From the *Value* drop-down lists, make sure that *Unique* is selected, then select *sum_total_price*. .. Change the *Label* to `Total sales`. -+ -You'll notice that the error is gone, but the number could use some formatting. -. To format the number, use the Canvas expression language. +. The error is gone, but the element could use some formatting. To format the number, use the Canvas expression language. .. Click *Expression editor*. + You're now looking at the raw data syntax that Canvas uses to display the element. -.. Look for `math "sum_total_price"`, then add `| formatNumber "$0a"`. +.. Change `metricFormat="0,0.[000]"` to `metricFormat="$0a"`. -.. To update the number, click *Run*. +.. Click *Run*. [role="screenshot"] image::images/canvas-metric-element.png[] @@ -89,21 +77,17 @@ image::images/canvas-metric-element.png[] To show what your data can do, add charts, graphs, progress monitors, and more to your workpad. -. Click *Add element*, then click *Area chart*. +. Click *Add element > Chart > Area*. -. To connect the element to your own data source, make sure that the element is selected, then click *Data*. +. Make sure that the element is selected, then click *Data > Demo data > Elasticsearch SQL*. -.. Click *Change your data source*, then click *Elasticsearch SQL*. - -.. To obtain the taxless total price by date, enter the following into the *Elasticsearch SQL query* field: +.. To obtain the taxless total price by date, enter the following in the *Query* field: + `SELECT order_date, taxless_total_price FROM "kibana_sample_data_ecommerce" ORDER BY order_date` -+ -Although you used the Elasticsearch SQL data source for the metric and area chart elements, each element can display a different data source. Pages and workpads often contain multiple data sources. -.. To verify that the data is correct, click *Preview*. If you like what you see, click *Save*. +.. Click *Save*. -. Specify how to display the data. +. Change the display options. .. Click *Display* @@ -117,34 +101,20 @@ image::images/canvas-chart-element.png[] [float] === Show how your data changes over time -To focus your data on a specific time range, add a time filter to your workpad. +To focus your data on a specific time range, add the time filter. -. Click *Add element*, then click *Time filter*. +. Click *Add element > Filter > Time filter*. -. Specify how to display the data. +. Click *Display* -.. Click *Display* - -.. To use the date time field from the sample data, enter `order_date` in the *Column* field, then click *Set*. +. To use the date time field from the sample data, enter `order_date` in the *Column* field, then click *Set*. [role="screenshot"] image::images/canvas-timefilter-element.png[] -To see how the data changes, set the time filter to *Last 7 days*. As you change the time filter options, the metrics dynamically update. - -Your workpad is now complete! From the workpad menu, use the icons to: - -* Configure the refresh rate for your data - -* Refresh the data that displays on your workpad - -* Display your workpad in fullscreen mode - -* Control the zoom options - -* Share your workpad +To see how the data changes, set the time filter to *Last 7 days*. As you change the time filter options, the elements automatically update. -* Hide the editing controls +Your workpad is now complete! [float] === Next steps diff --git a/docs/canvas/canvas-workpad.asciidoc b/docs/canvas/canvas-workpad.asciidoc index 42eedf55c404dc..ac2d3489201145 100644 --- a/docs/canvas/canvas-workpad.asciidoc +++ b/docs/canvas/canvas-workpad.asciidoc @@ -20,9 +20,7 @@ To create a workpad, choose one of the following options: To use the background colors, images, and data of your choice, start with a blank workpad. -. Open *Canvas*. - -. On the *Canvas workpads* view, click *Create workpad*. +. On the *Canvas workpads* page, click *Create workpad*. . Add a *Name* to your workpad. @@ -35,7 +33,7 @@ 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.gif[Canvas color picker] +image::images/canvas-background-color-picker.png[Canvas color picker] [float] [[canvas-template-workpad]] @@ -43,9 +41,7 @@ image::images/canvas-background-color-picker.gif[Canvas color picker] If you're unsure about where to start, you can use one of the preconfigured templates that come with Canvas. -. Open *Canvas*. - -. On the *Canvas workpads* view, select *Templates*. +. On the *Canvas workpads* page, select *Templates*. . Click the preconfigured template that you want to use. @@ -57,9 +53,7 @@ If you're unsure about where to start, you can use one of the preconfigured temp When you want to use a workpad that someone else has already started, import the JSON file into Canvas. -. Open *Canvas*. - -. On the *Canvas workpads* view, click and drag the file to the *Import workpad JSON file* field. +To import a workpad, go to the *Canvas workpads* page, then click and drag the file to the *Import workpad JSON file* field. [float] [[sample-data-workpad]] @@ -96,23 +90,27 @@ background-color: #3990e6; [[configure-auto-refresh-interval]] === Change the auto-refresh interval -Increase or decrease how often the data refreshes on your workpad. +Change how often the data refreshes on your workpad. -. In the top left corner, click the *Control settings* icon. +. Click *View > Auto refresh settings*. -. Under *Change auto-refresh interval*, select the interval you want to use. +. Select the interval you want to use, or *Set a custom interval*. + [role="screenshot"] image::images/canvas-refresh-interval.png[Element data refresh interval] - -TIP: To manually refresh the data, click the *Refresh data* icon. ++ +To manually refresh the data, click image:canvas/images/canvas-refresh-data.png[]. [float] [[zoom-in-out]] === Use the zoom options -In the upper left corner, click the *Zoom controls* icon, then select one of the 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] diff --git a/docs/canvas/images/canvas-embed_workpad.gif b/docs/canvas/images/canvas-embed_workpad.gif new file mode 100644 index 00000000000000..1cda5b572acefb Binary files /dev/null and b/docs/canvas/images/canvas-embed_workpad.gif differ diff --git a/docs/canvas/images/canvas-refresh-data.png b/docs/canvas/images/canvas-refresh-data.png new file mode 100644 index 00000000000000..7a71686f04491b Binary files /dev/null and b/docs/canvas/images/canvas-refresh-data.png differ diff --git a/docs/images/add-data-fv.png b/docs/images/add-data-fv.png new file mode 100755 index 00000000000000..45313d133822c1 Binary files /dev/null and b/docs/images/add-data-fv.png differ diff --git a/docs/images/add-data-tutorials.png b/docs/images/add-data-tutorials.png new file mode 100644 index 00000000000000..74deedc57b42ed Binary files /dev/null and b/docs/images/add-data-tutorials.png differ diff --git a/docs/images/canvas-add-image.gif b/docs/images/canvas-add-image.gif index a2263e22c4c49f..994ec6e1b4f288 100644 Binary files a/docs/images/canvas-add-image.gif and b/docs/images/canvas-add-image.gif differ diff --git a/docs/images/canvas-add-pages.gif b/docs/images/canvas-add-pages.gif index a1fa2286458364..c6e09d6f386ae5 100644 Binary files a/docs/images/canvas-add-pages.gif and b/docs/images/canvas-add-pages.gif differ diff --git a/docs/images/canvas-autoplay-interval.png b/docs/images/canvas-autoplay-interval.png new file mode 100644 index 00000000000000..68a7ca248d9eeb Binary files /dev/null and b/docs/images/canvas-autoplay-interval.png differ diff --git a/docs/images/canvas-background-color-picker.png b/docs/images/canvas-background-color-picker.png new file mode 100644 index 00000000000000..ec38b5c1c5f7e8 Binary files /dev/null and b/docs/images/canvas-background-color-picker.png differ diff --git a/docs/images/canvas-chart-element.png b/docs/images/canvas-chart-element.png index d0aa7db375a408..bf5e04bf89af54 100644 Binary files a/docs/images/canvas-chart-element.png and b/docs/images/canvas-chart-element.png differ diff --git a/docs/images/canvas-create-URL.gif b/docs/images/canvas-create-URL.gif index 0c9fbf7201d808..11327224fc897b 100644 Binary files a/docs/images/canvas-create-URL.gif and b/docs/images/canvas-create-URL.gif differ diff --git a/docs/images/canvas-element-select.gif b/docs/images/canvas-element-select.gif index bd0e49377262e7..1bfd1132f25c7a 100644 Binary files a/docs/images/canvas-element-select.gif and b/docs/images/canvas-element-select.gif differ diff --git a/docs/images/canvas-export-workpad.png b/docs/images/canvas-export-workpad.png index fa910daf948d76..213bbaa5a26d35 100644 Binary files a/docs/images/canvas-export-workpad.png and b/docs/images/canvas-export-workpad.png differ diff --git a/docs/images/canvas-generate-pdf.gif b/docs/images/canvas-generate-pdf.gif index 9ef16dc1e5017a..513f6b3b960f92 100644 Binary files a/docs/images/canvas-generate-pdf.gif and b/docs/images/canvas-generate-pdf.gif differ diff --git a/docs/images/canvas-image-element.png b/docs/images/canvas-image-element.png index f869ccb344a461..13c9090e77c760 100644 Binary files a/docs/images/canvas-image-element.png and b/docs/images/canvas-image-element.png differ diff --git a/docs/images/canvas-map-embed.gif b/docs/images/canvas-map-embed.gif index 59ef97e0ceae8e..c6ba5c29df42ab 100644 Binary files a/docs/images/canvas-map-embed.gif and b/docs/images/canvas-map-embed.gif differ diff --git a/docs/images/canvas-metric-element.png b/docs/images/canvas-metric-element.png index d9735a2fb3e919..03871dcc81862c 100644 Binary files a/docs/images/canvas-metric-element.png and b/docs/images/canvas-metric-element.png differ diff --git a/docs/images/canvas-refresh-interval.png b/docs/images/canvas-refresh-interval.png index 99006a5b8f12dd..62e88ad4bf7d04 100644 Binary files a/docs/images/canvas-refresh-interval.png and b/docs/images/canvas-refresh-interval.png differ diff --git a/docs/images/canvas-timefilter-element.png b/docs/images/canvas-timefilter-element.png index 8b8356ff5f7eaa..e210b0b3288c6a 100644 Binary files a/docs/images/canvas-timefilter-element.png and b/docs/images/canvas-timefilter-element.png differ diff --git a/docs/images/canvas_element_options.png b/docs/images/canvas_element_options.png index 191348d919b50c..41457bab4ff368 100644 Binary files a/docs/images/canvas_element_options.png and b/docs/images/canvas_element_options.png differ diff --git a/docs/images/canvas_save_element.png b/docs/images/canvas_save_element.png index a63f5135f2a0ea..8a601efab874a8 100644 Binary files a/docs/images/canvas_save_element.png and b/docs/images/canvas_save_element.png differ diff --git a/docs/management/managing-remote-clusters.asciidoc b/docs/management/managing-remote-clusters.asciidoc index 00ec5c7d2ddeae..51d9f42a0b83e3 100644 --- a/docs/management/managing-remote-clusters.asciidoc +++ b/docs/management/managing-remote-clusters.asciidoc @@ -31,6 +31,10 @@ to reproduce indices in the remote cluster on a local cluster. [role="screenshot"] image::images/add_remote_cluster.png[][UI for adding a remote cluster] +To create an index pattern to search across clusters, +use the same syntax that you’d use in a raw cross-cluster search request in {es}: :. +See <> for examples. + [float] [[manage-remote-clusters]] === Manage remote clusters diff --git a/docs/maps/images/fu_gs_select_source_file_upload.png b/docs/maps/images/fu_gs_select_source_file_upload.png index 6939f6a82b2971..4fe1162acb29ca 100644 Binary files a/docs/maps/images/fu_gs_select_source_file_upload.png and b/docs/maps/images/fu_gs_select_source_file_upload.png differ diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index 00acb73bd276f1..6137e028db3fd3 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -1,44 +1,105 @@ [[connect-to-elasticsearch]] -== Connect Kibana with Elasticsearch +== Adding data -Before you can start using Kibana, you need to tell it which Elasticsearch indices you want to explore. -The first time you access Kibana, you are prompted to define an _index pattern_ that matches the name of -one or more of your indices. That's it. That's all you need to configure to start using Kibana. You can -add index patterns at any time from the <>. +To start working with your data in {kib}, you can: -TIP: By default, Kibana connects to the Elasticsearch instance running on `localhost`. To connect to a -different Elasticsearch instance, modify the Elasticsearch URL in the `kibana.yml` configuration file and -restart Kibana. For information about using Kibana with your production nodes, see <>. +* Upload a CSV, JSON, or log file with the File Data Visualizer. -To configure the Elasticsearch indices you want to access with Kibana: +* Upload geospatial data with the GeoJSON Upload feature. -. Point your browser at port 5601 to access the Kibana UI. For example, `localhost:5601` or -`http://YOURDOMAIN.com:5601`. -+ -image:images/Start-Page.png[Kibana start page] -+ -. Specify an index pattern that matches the name of one or more of your Elasticsearch indices. The pattern -can include an asterisk (*) to matches zero or more characters in an index's name. When filling out your -index pattern, any matched indices will be displayed. -. Click *Next Step* to select the index field that contains the timestamp you want to use to perform time-based -comparisons. Kibana reads the index mapping to list all of the fields that contain a timestamp. If your -index doesn't have time-based data, choose *I don't want to use the Time Filter* option. -+ -. Click *Create index pattern* to add the index pattern. This first pattern is automatically configured as the default. -When you have more than one index pattern, you can designate which one to use as the default by clicking -on the star icon above the index pattern title from *Management > Index Patterns*. +* Index logs, metrics, events, or application data by setting up a Beats module. + +* Connect {kib} with existing {es} indices. + +If you're not ready to use your own data, you can add a <> +to see all that you can do in {kib}. + +[float] +[[upload-data-kibana]] +=== Upload a CSV, JSON, or log file + +To visualize data in a CSV, JSON, or log file, you can +upload it using the File Data Visualizer. On the home page, +click *Import a CSV, NDSON, or log file*, and then drag your file into the +File Data Visualizer. + +You can upload a file up to 100 MB. This value is configurable up to 1 GB in +<>. + +[role="screenshot"] +image::images/add-data-fv.png[File Data Visualizer] + +The File Data Visualizer uses the {ref}/ml-find-file-structure.html[find_file_structure API] to analyze +the uploaded file and to suggest ingest pipelines and mappings for your data. + +NOTE: This feature is not intended for use as part of a +repeated production process, but rather for the initial exploration of your data. + +[float] +[[upload-geoipdata-kibana]] +=== Upload geospatial data + +To visualize geospatial data in a point or shape file, you can upload it using the <> +feature in *Elastic Maps*, and then use that data as a layer in a map. +The data is also available for use in the broader Kibana ecosystem, for example, +in visualizations and Canvas workpads. +With GeoJSON Upload, you can upload a file up to 50 MB. + +[role="screenshot"] +image::images/fu_gs_select_source_file_upload.png[] -All done! Kibana is now connected to your Elasticsearch data. Kibana displays a read-only list of fields -configured for the matching index. [float] -[[explore]] -=== Start Exploring your Data! -You're ready to dive in to your data: +[[add-data-tutorial-kibana]] +=== Index metrics, log, security, and application data -* Search and browse your data interactively from the <> page. -* Chart and map your data from the <> page. -* Create and view custom dashboards from the <> page. +The built-in data tutorials can help you quickly get up and running with +metrics data, log analytics, security events, and application data. +These tutorials walk you through installing and configuring a +Beats data shipper to periodically collect and send data to {es}. +You can then use the pre-built dashboards to explore and analyze the data. -For a step-by-step introduction to these core Kibana concepts, see the <> tutorial. +You access the tutorials from the home page. +If a tutorial doesn’t exist for your data, go to the {beats-ref}/beats-reference.html[Beats overview] +to learn about other data shippers in the Beats family. + +[role="screenshot"] +image::images/add-data-tutorials.png[Add Data tutorials] + + +[float] +[[connect-to-es]] +=== Connect with {es} indices + +To visualize data in existing {es} indices, you must +create an index pattern that matches the names of the indices that you want to explore. +When you add data with the File Data Visualizer, GeoJSON Upload feature, +or built-in tutorial, an index pattern is created for you. + +. Go to *Stack Management*, and then click *Index Patterns*. + +. Click *Create index pattern*. + +. Specify an index pattern that matches the name of one or more of your Elasticsearch indices. ++ +For example, an index pattern can point to your Apache data from yesterday, +`filebeat-apache-4-3-2022`, or any index that matches the pattern, `filebeat-*`. +Using a wildcard is the more popular approach. + + +. Click *Next Step*, and then select the index field that contains the timestamp you want to use to perform time-based +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*. ++ +You must select a time field to use global time filters on your dashboards. + +. Click *Create index pattern*. ++ +{kib} is now configured to access your {es} indices. +You’ll see a list of fields configured for the matching index. +You can designate your index pattern as the default by clicking the star icon on this page. ++ +When searching in *Discover* and creating visualizations, you choose a pattern +from the index pattern menu to specify the {es} indices that contain the data you want to explore. diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index 98033c5a87f6f3..355684f7448a12 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -5,7 +5,7 @@ [partintro] -- -Canvas is a data visualization and presentation tool that sits within Kibana. With Canvas, you can pull live data directly from Elasticsearch, and combine the data with colors, images, text, and your imagination to create dynamic, multi-page, pixel-perfect displays. If you are a little bit creative, a little bit technical, and a whole lot curious, then Canvas is for you. +Canvas is a data visualization and presentation tool that sits within {kib}. With Canvas, you can pull live data directly from {es}, and combine the data with colors, images, text, and your imagination to create dynamic, multi-page, pixel-perfect displays. If you are a little bit creative, a little bit technical, and a whole lot curious, then Canvas is for you. With Canvas, you can: @@ -13,9 +13,7 @@ With Canvas, you can: * Customize your workpad with your own visualizations, such as images and text. -* Customize your data by pulling it directly from Elasticsearch. - -* Show off your data with charts, graphs, progress monitors, and more. +* Pull your data directly from Elasticsearch, then show it off with charts, graphs, progress monitors, and more. * Focus the data you want to display with filters. diff --git a/package.json b/package.json index 77d2a4156c67ff..5228a620a19d79 100644 --- a/package.json +++ b/package.json @@ -423,7 +423,7 @@ "eslint-plugin-prefer-object-spread": "^1.2.1", "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-react": "^7.17.0", - "eslint-plugin-react-hooks": "^2.3.0", + "eslint-plugin-react-hooks": "^4.0.4", "eslint-plugin-react-perf": "^3.2.3", "exit-hook": "^2.2.0", "faker": "1.1.0", diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 0ab0048619358b..8e2fd1c9182ff7 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -14,6 +14,7 @@ "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@types/parse-link-header": "^1.0.0", + "@types/puppeteer": "^3.0.0", "@types/strip-ansi": "^5.2.1", "@types/xml2js": "^0.4.5", "diff": "^4.0.1" @@ -25,6 +26,7 @@ "getopts": "^2.2.4", "glob": "^7.1.2", "parse-link-header": "^1.0.1", + "puppeteer": "^3.3.0", "strip-ansi": "^5.2.0", "rxjs": "^6.5.3", "tar-fs": "^1.16.3", diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index 3a938280a90c58..e6b180d979f175 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -51,3 +51,5 @@ export { runFailedTestsReporterCli } from './failed_tests_reporter'; export { makeJunitReportPath } from './junit_report_path'; export { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix'; + +export * from './page_load_metrics'; diff --git a/packages/kbn-test/src/page_load_metrics/capture_page_load_metrics.ts b/packages/kbn-test/src/page_load_metrics/capture_page_load_metrics.ts new file mode 100644 index 00000000000000..013d49a29a51cf --- /dev/null +++ b/packages/kbn-test/src/page_load_metrics/capture_page_load_metrics.ts @@ -0,0 +1,81 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ToolingLog } from '@kbn/dev-utils'; +import { NavigationOptions, createUrl, navigateToApps } from './navigation'; + +export async function capturePageLoadMetrics(log: ToolingLog, options: NavigationOptions) { + const responsesByPageView = await navigateToApps(log, options); + + const assetSizeMeasurements = new Map(); + + const numberOfPagesVisited = responsesByPageView.size; + + for (const [, frameResponses] of responsesByPageView) { + for (const [, { url, dataLength }] of frameResponses) { + if (url.length === 0) { + throw new Error('navigateToApps(); failed to identify the url of the request'); + } + if (assetSizeMeasurements.has(url)) { + assetSizeMeasurements.set(url, [dataLength].concat(assetSizeMeasurements.get(url) || [])); + } else { + assetSizeMeasurements.set(url, [dataLength]); + } + } + } + + return Array.from(assetSizeMeasurements.entries()) + .map(([url, measurements]) => { + const baseUrl = createUrl('/', options.appConfig.url); + const relativeUrl = url + // remove the baseUrl (expect the trailing slash) to make url relative + .replace(baseUrl.slice(0, -1), '') + // strip the build number from asset urls + .replace(/^\/\d+\//, '/'); + return [relativeUrl, measurements] as const; + }) + .filter(([url, measurements]) => { + if (measurements.length !== numberOfPagesVisited) { + // ignore urls seen only on some pages + return false; + } + + if (url.startsWith('data:')) { + // ignore data urls since they are already counted by other assets + return false; + } + + if (url.startsWith('/api/') || url.startsWith('/internal/')) { + // ignore api requests since they don't have deterministic sizes + return false; + } + + const allMetricsAreEqual = measurements.every((x, i) => + i === 0 ? true : x === measurements[i - 1] + ); + if (!allMetricsAreEqual) { + throw new Error(`measurements for url [${url}] are not equal [${measurements.join(',')}]`); + } + + return true; + }) + .map(([url, measurements]) => { + return { group: 'page load asset size', id: url, value: measurements[0] }; + }); +} diff --git a/packages/kbn-test/src/page_load_metrics/cli.ts b/packages/kbn-test/src/page_load_metrics/cli.ts new file mode 100644 index 00000000000000..95421384c79cb0 --- /dev/null +++ b/packages/kbn-test/src/page_load_metrics/cli.ts @@ -0,0 +1,90 @@ +/* + * 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 Url from 'url'; + +import { run, createFlagError } from '@kbn/dev-utils'; +import { resolve, basename } from 'path'; +import { capturePageLoadMetrics } from './capture_page_load_metrics'; + +const defaultScreenshotsDir = resolve(__dirname, 'screenshots'); + +export function runPageLoadMetricsCli() { + run( + async ({ flags, log }) => { + const kibanaUrl = flags['kibana-url']; + if (!kibanaUrl || typeof kibanaUrl !== 'string') { + throw createFlagError('Expect --kibana-url to be a string'); + } + + const parsedUrl = Url.parse(kibanaUrl); + + const [username, password] = parsedUrl.auth + ? parsedUrl.auth.split(':') + : [flags.username, flags.password]; + + if (typeof username !== 'string' || typeof password !== 'string') { + throw createFlagError( + 'Mising username and/or password, either specify in --kibana-url or pass --username and --password' + ); + } + + const headless = !flags.head; + + const screenshotsDir = flags.screenshotsDir || defaultScreenshotsDir; + + if (typeof screenshotsDir !== 'string' || screenshotsDir === basename(screenshotsDir)) { + throw createFlagError('Expect screenshotsDir to be valid path string'); + } + + const metrics = await capturePageLoadMetrics(log, { + headless, + appConfig: { + url: kibanaUrl, + username, + password, + }, + screenshotsDir, + }); + for (const metric of metrics) { + log.info(`${metric.id}: ${metric.value}`); + } + }, + { + description: `Loads several pages with Puppeteer to capture the size of assets`, + flags: { + string: ['kibana-url', 'username', 'password', 'screenshotsDir'], + boolean: ['head'], + default: { + username: 'elastic', + password: 'changeme', + debug: true, + screenshotsDir: defaultScreenshotsDir, + }, + help: ` + --kibana-url Url for Kibana we should connect to, can include login info + --head Run puppeteer with graphical user interface + --username Set username, defaults to 'elastic' + --password Set password, defaults to 'changeme' + --screenshotsDir Set screenshots directory, defaults to '${defaultScreenshotsDir}' + `, + }, + } + ); +} diff --git a/packages/kbn-test/src/page_load_metrics/event.ts b/packages/kbn-test/src/page_load_metrics/event.ts new file mode 100644 index 00000000000000..481954bbf672e2 --- /dev/null +++ b/packages/kbn-test/src/page_load_metrics/event.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface ResponseReceivedEvent { + frameId: string; + loaderId: string; + requestId: string; + response: Record; + timestamp: number; + type: string; +} + +export interface DataReceivedEvent { + encodedDataLength: number; + dataLength: number; + requestId: string; + timestamp: number; +} diff --git a/packages/kbn-test/src/page_load_metrics/index.ts b/packages/kbn-test/src/page_load_metrics/index.ts new file mode 100644 index 00000000000000..4309d558518a60 --- /dev/null +++ b/packages/kbn-test/src/page_load_metrics/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './cli'; +export { capturePageLoadMetrics } from './capture_page_load_metrics'; diff --git a/packages/kbn-test/src/page_load_metrics/navigation.ts b/packages/kbn-test/src/page_load_metrics/navigation.ts new file mode 100644 index 00000000000000..21dc681951b212 --- /dev/null +++ b/packages/kbn-test/src/page_load_metrics/navigation.ts @@ -0,0 +1,165 @@ +/* + * 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 Fs from 'fs'; +import Url from 'url'; +import _ from 'lodash'; +import puppeteer from 'puppeteer'; +import { resolve } from 'path'; +import { ToolingLog } from '@kbn/dev-utils'; +import { ResponseReceivedEvent, DataReceivedEvent } from './event'; + +export interface NavigationOptions { + headless: boolean; + appConfig: { url: string; username: string; password: string }; + screenshotsDir: string; +} + +export type NavigationResults = Map>; + +interface FrameResponse { + url: string; + dataLength: number; +} + +function joinPath(pathA: string, pathB: string) { + return `${pathA.endsWith('/') ? pathA.slice(0, -1) : pathA}/${ + pathB.startsWith('/') ? pathB.slice(1) : pathB + }`; +} + +export function createUrl(path: string, url: string) { + const baseUrl = Url.parse(url); + return Url.format({ + protocol: baseUrl.protocol, + hostname: baseUrl.hostname, + port: baseUrl.port, + pathname: joinPath(baseUrl.pathname || '', path), + }); +} + +async function loginToKibana( + log: ToolingLog, + browser: puppeteer.Browser, + options: NavigationOptions +) { + log.debug(`log in to the app..`); + const page = await browser.newPage(); + const loginUrl = createUrl('/login', options.appConfig.url); + await page.goto(loginUrl, { + waitUntil: 'networkidle0', + }); + await page.type('[data-test-subj="loginUsername"]', options.appConfig.username); + await page.type('[data-test-subj="loginPassword"]', options.appConfig.password); + await page.click('[data-test-subj="loginSubmit"]'); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); + await page.close(); +} + +export async function navigateToApps(log: ToolingLog, options: NavigationOptions) { + const browser = await puppeteer.launch({ headless: options.headless, args: ['--no-sandbox'] }); + const devToolsResponses: NavigationResults = new Map(); + const apps = [ + { path: '/app/discover', locator: '[data-test-subj="discover-sidebar"]' }, + { path: '/app/home', locator: '[data-test-subj="homeApp"]' }, + { path: '/app/canvas', locator: '[data-test-subj="create-workpad-button"]' }, + { path: '/app/maps', locator: '[title="Maps"]' }, + { path: '/app/apm', locator: '[data-test-subj="apmMainContainer"]' }, + ]; + + await loginToKibana(log, browser, options); + + await Promise.all( + apps.map(async (app) => { + const page = await browser.newPage(); + page.setCacheEnabled(false); + page.setDefaultNavigationTimeout(0); + const frameResponses = new Map(); + devToolsResponses.set(app.path, frameResponses); + + const client = await page.target().createCDPSession(); + await client.send('Network.enable'); + + function getRequestData(requestId: string) { + if (!frameResponses.has(requestId)) { + frameResponses.set(requestId, { url: '', dataLength: 0 }); + } + + return frameResponses.get(requestId)!; + } + + client.on('Network.responseReceived', (event: ResponseReceivedEvent) => { + getRequestData(event.requestId).url = event.response.url; + }); + + client.on('Network.dataReceived', (event: DataReceivedEvent) => { + getRequestData(event.requestId).dataLength += event.dataLength; + }); + + const url = createUrl(app.path, options.appConfig.url); + log.debug(`goto ${url}`); + await page.goto(url, { + waitUntil: 'networkidle0', + }); + + let readyAttempt = 0; + let selectorFound = false; + while (!selectorFound) { + readyAttempt += 1; + try { + await page.waitForSelector(app.locator, { timeout: 5000 }); + selectorFound = true; + } catch (error) { + log.error( + `Page '${app.path}' was not loaded properly, unable to find '${ + app.locator + }', url: ${page.url()}` + ); + + if (readyAttempt < 6) { + continue; + } + + const failureDir = resolve(options.screenshotsDir, 'failure'); + const screenshotPath = resolve( + failureDir, + `${app.path.slice(1).split('/').join('_')}_navigation.png` + ); + Fs.mkdirSync(failureDir, { recursive: true }); + + await page.bringToFront(); + await page.screenshot({ + path: screenshotPath, + type: 'png', + fullPage: true, + }); + log.debug(`Saving screenshot to ${screenshotPath}`); + + throw new Error(`Page load timeout: ${app.path} not loaded after 30 seconds`); + } + } + + await page.close(); + }) + ); + + await browser.close(); + + return devToolsResponses; +} diff --git a/scripts/page_load_metrics.js b/scripts/page_load_metrics.js new file mode 100644 index 00000000000000..37500c26e0b20b --- /dev/null +++ b/scripts/page_load_metrics.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('../src/setup_node_env'); +require('@kbn/test').runPageLoadMetricsCli(); diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 9c2ab0819776ba..d98770842a0f09 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -254,6 +254,9 @@ export const npSetup = { }, share: { register: () => {}, + urlGenerators: { + registerUrlGenerator: () => {}, + }, }, devTools: { register: () => {}, diff --git a/src/legacy/ui/public/styles/_legacy/_base.scss b/src/legacy/ui/public/styles/_legacy/_base.scss index fd0a1335f9685e..877ae033ae5846 100644 --- a/src/legacy/ui/public/styles/_legacy/_base.scss +++ b/src/legacy/ui/public/styles/_legacy/_base.scss @@ -64,10 +64,6 @@ input[type='checkbox'], padding-bottom: $euiSizeS; } - .globalQueryBar { - padding: 0px $euiSizeS $euiSizeS $euiSizeS; - } - > nav, > navbar { z-index: 2 !important; diff --git a/src/plugins/charts/public/services/theme/theme.ts b/src/plugins/charts/public/services/theme/theme.ts index 166e1c539688a0..e1e71573caa3a5 100644 --- a/src/plugins/charts/public/services/theme/theme.ts +++ b/src/plugins/charts/public/services/theme/theme.ts @@ -42,8 +42,10 @@ export class ThemeService { /** A React hook for consuming the charts theme */ public useChartsTheme = () => { + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const [value, update] = useState(this.chartsDefaultTheme); + /* eslint-disable-next-line react-hooks/rules-of-hooks */ useEffect(() => { const s = this.chartsTheme$.subscribe(update); return () => s.unsubscribe(); diff --git a/src/plugins/console/public/application/containers/editor/editor.tsx b/src/plugins/console/public/application/containers/editor/editor.tsx index 0bfe837f2cd90f..66d3cbab20ac52 100644 --- a/src/plugins/console/public/application/containers/editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/editor.tsx @@ -47,6 +47,7 @@ export const Editor = memo(({ loading }: Props) => { INITIAL_PANEL_WIDTH, ]); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const onPanelWidthChange = useCallback( debounce((widths: number[]) => { storage.set(StorageKeys.WIDTH, widths); diff --git a/src/plugins/console/public/application/hooks/use_save_current_text_object.ts b/src/plugins/console/public/application/hooks/use_save_current_text_object.ts index ab517ba1bfdd13..1bd1a7fb09bd1f 100644 --- a/src/plugins/console/public/application/hooks/use_save_current_text_object.ts +++ b/src/plugins/console/public/application/hooks/use_save_current_text_object.ts @@ -32,6 +32,7 @@ export const useSaveCurrentTextObject = () => { const { currentTextObject } = useEditorReadContext(); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ return useCallback( throttle( (text: string) => { diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index a59d1e8c546d4b..206ef4f3d4313a 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -19,8 +19,9 @@ import _, { uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui'; -import React from 'react'; +import { EUI_MODAL_CANCEL_BUTTON, EuiCheckboxGroup } from '@elastic/eui'; +import { EuiCheckboxGroupIdToSelectedMap } from '@elastic/eui/src/components/form/checkbox/checkbox_group'; +import React, { useState, ReactElement } from 'react'; import ReactDOM from 'react-dom'; import angular from 'angular'; @@ -94,6 +95,25 @@ export interface DashboardAppControllerDependencies extends RenderDeps { navigation: NavigationStart; } +enum UrlParams { + SHOW_TOP_MENU = 'show-top-menu', + SHOW_QUERY_INPUT = 'show-query-input', + SHOW_TIME_FILTER = 'show-time-filter', + SHOW_FILTER_BAR = 'show-filter-bar', + HIDE_FILTER_BAR = 'hide-filter-bar', +} + +interface UrlParamsSelectedMap { + [UrlParams.SHOW_TOP_MENU]: boolean; + [UrlParams.SHOW_QUERY_INPUT]: boolean; + [UrlParams.SHOW_TIME_FILTER]: boolean; + [UrlParams.SHOW_FILTER_BAR]: boolean; +} + +interface UrlParamValues extends Omit { + [UrlParams.HIDE_FILTER_BAR]: boolean; +} + export class DashboardAppController { // Part of the exposed plugin API - do not remove without careful consideration. appStatus: { @@ -133,8 +153,16 @@ export class DashboardAppController { const filterManager = queryService.filterManager; const queryFilter = filterManager; const timefilter = queryService.timefilter.timefilter; - let showSearchBar = true; - let showQueryBar = true; + const isEmbeddedExternally = Boolean($routeParams.embed); + + // url param rules should only apply when embedded (e.g. url?embed=true) + const shouldForceDisplay = (param: string): boolean => + isEmbeddedExternally && Boolean($routeParams[param]); + + const forceShowTopNavMenu = shouldForceDisplay(UrlParams.SHOW_TOP_MENU); + const forceShowQueryInput = shouldForceDisplay(UrlParams.SHOW_QUERY_INPUT); + const forceShowDatePicker = shouldForceDisplay(UrlParams.SHOW_TIME_FILTER); + const forceHideFilterBar = shouldForceDisplay(UrlParams.HIDE_FILTER_BAR); let lastReloadRequestTime = 0; const dash = ($scope.dash = $route.current.locals.dash); @@ -251,9 +279,6 @@ export class DashboardAppController { } }; - const showFilterBar = () => - $scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode(); - const getEmptyScreenProps = ( shouldShowEditHelp: boolean, isEmptyInReadOnlyMode: boolean @@ -299,6 +324,7 @@ export class DashboardAppController { viewMode: dashboardStateManager.getViewMode(), panels: embeddablesMap, isFullScreenMode: dashboardStateManager.getFullScreenMode(), + isEmbeddedExternally, isEmptyState: shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadonlyMode, useMargins: dashboardStateManager.getUseMargins(), lastReloadRequestTime, @@ -590,17 +616,33 @@ export class DashboardAppController { dashboardStateManager.setSavedQueryId(savedQueryId); }; + const shouldShowFilterBar = (forceHide: boolean): boolean => + !forceHide && ($scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode()); + + const shouldShowNavBarComponent = (forceShow: boolean): boolean => + (forceShow || $scope.isVisible) && !dashboardStateManager.getFullScreenMode(); + const getNavBarProps = () => { const isFullScreenMode = dashboardStateManager.getFullScreenMode(); const screenTitle = dashboardStateManager.getTitle(); + const showTopNavMenu = shouldShowNavBarComponent(forceShowTopNavMenu); + const showQueryInput = shouldShowNavBarComponent(forceShowQueryInput); + const showDatePicker = shouldShowNavBarComponent(forceShowDatePicker); + const showQueryBar = showQueryInput || showDatePicker; + const showFilterBar = shouldShowFilterBar(forceHideFilterBar); + const showSearchBar = showQueryBar || showFilterBar; + return { appName: 'dashboard', - config: $scope.isVisible ? $scope.topNavMenu : undefined, + config: showTopNavMenu ? $scope.topNavMenu : undefined, className: isFullScreenMode ? 'kbnTopNavMenu-isFullScreen' : undefined, screenTitle, + showTopNavMenu, showSearchBar, showQueryBar, - showFilterBar: showFilterBar(), + showQueryInput, + showDatePicker, + showFilterBar, indexPatterns: $scope.indexPatterns, showSaveQuery: $scope.showSaveQuery, query: $scope.model.query, @@ -798,7 +840,6 @@ export class DashboardAppController { } = {}; navActions[TopNavIds.FULL_SCREEN] = () => { dashboardStateManager.setFullScreenMode(true); - showQueryBar = false; updateNavBar(); }; navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(ViewMode.VIEW); @@ -923,6 +964,80 @@ export class DashboardAppController { if (share) { // the share button is only availabale if "share" plugin contract enabled navActions[TopNavIds.SHARE] = (anchorElement) => { + const EmbedUrlParamExtension = ({ + setParamValue, + }: { + setParamValue: (paramUpdate: UrlParamValues) => void; + }): ReactElement => { + const [urlParamsSelectedMap, setUrlParamsSelectedMap] = useState({ + [UrlParams.SHOW_TOP_MENU]: false, + [UrlParams.SHOW_QUERY_INPUT]: false, + [UrlParams.SHOW_TIME_FILTER]: false, + [UrlParams.SHOW_FILTER_BAR]: true, + }); + + const checkboxes = [ + { + id: UrlParams.SHOW_TOP_MENU, + label: i18n.translate('dashboard.embedUrlParamExtension.topMenu', { + defaultMessage: 'Top menu', + }), + }, + { + id: UrlParams.SHOW_QUERY_INPUT, + label: i18n.translate('dashboard.embedUrlParamExtension.query', { + defaultMessage: 'Query', + }), + }, + { + id: UrlParams.SHOW_TIME_FILTER, + label: i18n.translate('dashboard.embedUrlParamExtension.timeFilter', { + defaultMessage: 'Time filter', + }), + }, + { + id: UrlParams.SHOW_FILTER_BAR, + label: i18n.translate('dashboard.embedUrlParamExtension.filterBar', { + defaultMessage: 'Filter bar', + }), + }, + ]; + + const handleChange = (param: string): void => { + const urlParamsSelectedMapUpdate = { + ...urlParamsSelectedMap, + [param]: !urlParamsSelectedMap[param as keyof UrlParamsSelectedMap], + }; + setUrlParamsSelectedMap(urlParamsSelectedMapUpdate); + + const urlParamValues = { + [UrlParams.SHOW_TOP_MENU]: urlParamsSelectedMap[UrlParams.SHOW_TOP_MENU], + [UrlParams.SHOW_QUERY_INPUT]: urlParamsSelectedMap[UrlParams.SHOW_QUERY_INPUT], + [UrlParams.SHOW_TIME_FILTER]: urlParamsSelectedMap[UrlParams.SHOW_TIME_FILTER], + [UrlParams.HIDE_FILTER_BAR]: !urlParamsSelectedMap[UrlParams.SHOW_FILTER_BAR], + [param === UrlParams.SHOW_FILTER_BAR ? UrlParams.HIDE_FILTER_BAR : param]: + param === UrlParams.SHOW_FILTER_BAR + ? urlParamsSelectedMap[UrlParams.SHOW_FILTER_BAR] + : !urlParamsSelectedMap[param as keyof UrlParamsSelectedMap], + }; + setParamValue(urlParamValues); + }; + + return ( + + ); + }; + share.toggleShareContextMenu({ anchorElement, allowEmbed: true, @@ -935,6 +1050,12 @@ export class DashboardAppController { title: dash.title, }, isDirty: dashboardStateManager.getIsDirty(), + embedUrlParamExtensions: [ + { + paramName: 'embed', + component: EmbedUrlParamExtension, + }, + ], }); }; } @@ -955,8 +1076,6 @@ export class DashboardAppController { const visibleSubscription = chrome.getIsVisible$().subscribe((isVisible) => { $scope.$evalAsync(() => { $scope.isVisible = isVisible; - showSearchBar = isVisible || showFilterBar(); - showQueryBar = !dashboardStateManager.getFullScreenMode() && isVisible; updateNavBar(); }); }); diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 7e25d80c9d6191..6ab19445e50cd3 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -57,6 +57,7 @@ export interface DashboardContainerInput extends ContainerInput { useMargins: boolean; title: string; description?: string; + isEmbeddedExternally: boolean; isFullScreenMode: boolean; panels: { [panelId: string]: DashboardPanelState; @@ -105,6 +106,7 @@ export class DashboardContainer extends Container { return { panels: {}, + isEmbeddedExternally: false, isFullScreenMode: false, useMargins: true, }; diff --git a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts index 62a39ee898d3a6..1b060c186db973 100644 --- a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts @@ -108,16 +108,32 @@ interface IplacementDirection { fits: boolean; } +/** + * Compare grid data by an ending y coordinate. Grid data with a smaller ending y coordinate + * comes first. + * @param a + * @param b + */ +function comparePanels(a: GridData, b: GridData): number { + if (a.y + a.h < b.y + b.h) { + return -1; + } + if (a.y + a.h > b.y + b.h) { + return 1; + } + // a.y === b.y + if (a.x + a.w <= b.x + b.w) { + return -1; + } + return 1; +} + export function placePanelBeside({ width, height, currentPanels, placeBesideId, }: IPanelPlacementBesideArgs): Omit { - // const clonedPanels = _.cloneDeep(currentPanels); - if (!placeBesideId) { - throw new Error('Place beside method called without placeBesideId'); - } const panelToPlaceBeside = currentPanels[placeBesideId]; if (!panelToPlaceBeside) { throw new PanelNotFoundError(); @@ -130,10 +146,11 @@ export function placePanelBeside({ const possiblePlacementDirections: IplacementDirection[] = [ { grid: { x: beside.x + beside.w, y: beside.y, w: width, h: height }, fits: true }, // right - { grid: { x: beside.x - width, y: beside.y, w: width, h: height }, fits: true }, // left + { grid: { x: 0, y: beside.y + beside.h, w: width, h: height }, fits: true }, // left side of next row { grid: { x: beside.x, y: beside.y + beside.h, w: width, h: height }, fits: true }, // bottom ]; + // first, we check if there is place around the current panel for (const direction of possiblePlacementDirections) { if ( direction.grid.x >= 0 && @@ -156,13 +173,32 @@ export function placePanelBeside({ } } // if we get here that means there is no blank space around the panel we are placing beside. This means it's time to mess up the dashboard's groove. Fun! - const [, , bottomPlacement] = possiblePlacementDirections; - for (const currentPanelGrid of otherPanels) { - if (bottomPlacement.grid.y <= currentPanelGrid.y) { - const movedPanel = _.cloneDeep(currentPanels[currentPanelGrid.i]); - movedPanel.gridData.y = movedPanel.gridData.y + bottomPlacement.grid.h; - currentPanels[currentPanelGrid.i] = movedPanel; + /** + * 1. sort the panels in the grid + * 2. place the cloned panel to the bottom + * 3. reposition the panels after the cloned panel in the grid + */ + const grid = otherPanels.sort(comparePanels); + + let position = 0; + for (position; position < grid.length; position++) { + if (beside.i === grid[position].i) { + break; } } + const bottomPlacement = possiblePlacementDirections[2]; + // place to the bottom and move all other panels + let originalPositionInTheGrid = grid[position + 1].i; + const diff = + bottomPlacement.grid.y + + bottomPlacement.grid.h - + currentPanels[originalPositionInTheGrid].gridData.y; + + for (let j = position + 1; j < grid.length; j++) { + originalPositionInTheGrid = grid[j].i; + const movedPanel = _.cloneDeep(currentPanels[originalPositionInTheGrid]); + movedPanel.gridData.y = movedPanel.gridData.y + diff; + currentPanels[originalPositionInTheGrid] = movedPanel; + } return bottomPlacement.grid; } diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index ae239bc27fdbaa..971e096641f177 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -35,6 +35,7 @@ interface State { title: string; description?: string; panels: { [key: string]: PanelState }; + isEmbeddedExternally?: boolean; isEmptyState?: boolean; } @@ -51,6 +52,7 @@ export class DashboardViewport extends React.Component {isFullScreenMode && ( )} {renderEmpty && renderEmpty()} @@ -115,7 +121,14 @@ export class DashboardViewport extends React.Component )} diff --git a/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts b/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts index 4ceac90672cb34..825a69155ba221 100644 --- a/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts +++ b/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts @@ -27,6 +27,7 @@ export function getSampleDashboardInput( id: '123', filters: [], useMargins: false, + isEmbeddedExternally: false, isFullScreenMode: false, title: 'My Dashboard', query: { diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 08740b21f39a44..f6c36aa69febfe 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -167,15 +167,26 @@ export class DashboardPlugin const getStartServices = async () => { const [coreStart, deps] = await core.getStartServices(); - const useHideChrome = () => { + const useHideChrome = ({ toggleChrome } = { toggleChrome: true }) => { React.useEffect(() => { - coreStart.chrome.setIsVisible(false); - return () => coreStart.chrome.setIsVisible(true); - }, []); + if (toggleChrome) { + coreStart.chrome.setIsVisible(false); + } + + return () => { + if (toggleChrome) { + coreStart.chrome.setIsVisible(true); + } + }; + }, [toggleChrome]); }; - const ExitFullScreenButton: React.FC = (props) => { - useHideChrome(); + const ExitFullScreenButton: React.FC< + ExitFullScreenButtonProps & { + toggleChrome: boolean; + } + > = ({ toggleChrome, ...props }) => { + useHideChrome({ toggleChrome }); return ; }; return { diff --git a/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss b/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss index 1c47c28097454e..731c9f4d7f18d8 100644 --- a/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss +++ b/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss @@ -3,6 +3,10 @@ padding: 0px $euiSizeS $euiSizeS $euiSizeS; } +.globalQueryBar:first-child { + padding-top: $euiSizeS; +} + .globalQueryBar:not(:empty) { padding-bottom: $euiSizeS; } diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 18ed632e0a8ecc..81e84e31980726 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -135,12 +135,14 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) queryRef.current = props.query; setQuery(props.query || defaultQuery); } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [defaultQuery, props.query]); useEffect(() => { if (props.onQuerySubmit !== onQuerySubmitRef.current) { onQuerySubmitRef.current = props.onQuerySubmit; } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [props.onQuerySubmit]); // handle service state updates. diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index 0b3a07e98624ef..14dd399697b568 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -1,6 +1,7 @@ { "id": "discover", "version": "kibana", + "optionalPlugins": ["share"], "server": true, "ui": true, "requiredPlugins": [ diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 359d91325f064e..4154fdfeb3ff48 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -27,3 +27,4 @@ export function plugin(initializerContext: PluginInitializerContext) { export { SavedSearch, SavedSearchLoader, createSavedSearchesLoader } from './saved_searches'; export { ISearchEmbeddable, SEARCH_EMBEDDABLE_TYPE, SearchInput } from './application/embeddable'; +export { DISCOVER_APP_URL_GENERATOR } from './url_generator'; diff --git a/src/plugins/discover/public/mocks.ts b/src/plugins/discover/public/mocks.ts index c394fe2c11a714..e4314426bfce53 100644 --- a/src/plugins/discover/public/mocks.ts +++ b/src/plugins/discover/public/mocks.ts @@ -34,6 +34,9 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { savedSearchLoader: {} as any, + urlGenerator: { + createUrl: jest.fn(), + } as any, }; return startContract; }; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 5a031872913c04..6d9cffbe0ad738 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -34,7 +34,7 @@ import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public'; import { EmbeddableStart, EmbeddableSetup } from 'src/plugins/embeddable/public'; import { ChartsPluginStart } from 'src/plugins/charts/public'; import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; -import { SharePluginStart } from 'src/plugins/share/public'; +import { SharePluginStart, SharePluginSetup, UrlGeneratorContract } from 'src/plugins/share/public'; import { VisualizationsStart, VisualizationsSetup } from 'src/plugins/visualizations/public'; import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; @@ -43,7 +43,7 @@ import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../d import { SavedObjectLoader } from '../../saved_objects/public'; import { createKbnUrlTracker } from '../../kibana_utils/public'; import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; - +import { UrlGeneratorState } from '../../share/public'; import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types'; import { DocViewsRegistry } from './application/doc_views/doc_views_registry'; import { DocViewTable } from './application/components/table/table'; @@ -59,6 +59,17 @@ import { import { createSavedSearchesLoader } from './saved_searches'; import { registerFeature } from './register_feature'; import { buildServices } from './build_services'; +import { + DiscoverUrlGeneratorState, + DISCOVER_APP_URL_GENERATOR, + DiscoverUrlGenerator, +} from './url_generator'; + +declare module '../../share/public' { + export interface UrlGeneratorStateMapping { + [DISCOVER_APP_URL_GENERATOR]: UrlGeneratorState; + } +} /** * @public @@ -76,12 +87,31 @@ export interface DiscoverSetup { export interface DiscoverStart { savedSearchLoader: SavedObjectLoader; + + /** + * `share` plugin URL generator for Discover app. Use it to generate links into + * Discover application, example: + * + * ```ts + * const url = await plugins.discover.urlGenerator.createUrl({ + * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', + * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', + * timeRange: { + * to: 'now', + * from: 'now-15m', + * mode: 'relative', + * }, + * }); + * ``` + */ + readonly urlGenerator: undefined | UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>; } /** * @internal */ export interface DiscoverSetupPlugins { + share?: SharePluginSetup; uiActions: UiActionsSetup; embeddable: EmbeddableSetup; kibanaLegacy: KibanaLegacySetup; @@ -122,6 +152,7 @@ export class DiscoverPlugin private stopUrlTracking: (() => void) | undefined = undefined; private servicesInitialized: boolean = false; private innerAngularInitialized: boolean = false; + private urlGenerator?: DiscoverStart['urlGenerator']; /** * why are those functions public? they are needed for some mocha tests @@ -131,6 +162,17 @@ export class DiscoverPlugin public initializeServices?: () => Promise<{ core: CoreStart; plugins: DiscoverStartPlugins }>; setup(core: CoreSetup, plugins: DiscoverSetupPlugins) { + const baseUrl = core.http.basePath.prepend('/app/discover'); + + if (plugins.share) { + this.urlGenerator = plugins.share.urlGenerators.registerUrlGenerator( + new DiscoverUrlGenerator({ + appBasePath: baseUrl, + useHash: core.uiSettings.get('state:storeInSessionStorage'), + }) + ); + } + this.docViewsRegistry = new DocViewsRegistry(); setDocViewsRegistry(this.docViewsRegistry); this.docViewsRegistry.addDocView({ @@ -158,7 +200,7 @@ export class DiscoverPlugin // so history is lazily created (when app is mounted) // this prevents redundant `#` when not in discover app getHistory: getScopedHistory, - baseUrl: core.http.basePath.prepend('/app/discover'), + baseUrl, defaultSubUrl: '#/', storageKey: `lastUrl:${core.http.basePath.get()}:discover`, navLinkUpdater$: this.appStateUpdater, @@ -266,6 +308,7 @@ export class DiscoverPlugin }; return { + urlGenerator: this.urlGenerator, savedSearchLoader: createSavedSearchesLoader({ savedObjectsClient: core.savedObjects.client, indexPatterns: plugins.data.indexPatterns, diff --git a/src/plugins/discover/public/url_generator.test.ts b/src/plugins/discover/public/url_generator.test.ts new file mode 100644 index 00000000000000..cf9beb246fea29 --- /dev/null +++ b/src/plugins/discover/public/url_generator.test.ts @@ -0,0 +1,259 @@ +/* + * 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 { DiscoverUrlGenerator } from './url_generator'; +import { hashedItemStore, getStatesFromKbnUrl } from '../../kibana_utils/public'; +// eslint-disable-next-line +import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock'; +import { FilterStateStore } from '../../data/common'; + +const appBasePath: string = 'xyz/app/discover'; +const indexPatternId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002'; +const savedSearchId: string = '571aaf70-4c88-11e8-b3d7-01146121b73d'; + +interface SetupParams { + useHash?: boolean; +} + +const setup = async ({ useHash = false }: SetupParams = {}) => { + const generator = new DiscoverUrlGenerator({ + appBasePath, + useHash, + }); + + return { + generator, + }; +}; + +beforeEach(() => { + // @ts-ignore + hashedItemStore.storage = mockStorage; +}); + +describe('Discover url generator', () => { + test('can create a link to Discover with no state and no saved search', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({}); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(url.startsWith(appBasePath)).toBe(true); + expect(_a).toEqual({}); + expect(_g).toEqual({}); + }); + + test('can create a link to a saved search in Discover', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ savedSearchId }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(url.startsWith(`${appBasePath}#/${savedSearchId}`)).toBe(true); + expect(_a).toEqual({}); + expect(_g).toEqual({}); + }); + + test('can specify specific index pattern', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + indexPatternId, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({ + index: indexPatternId, + }); + expect(_g).toEqual({}); + }); + + test('can specify specific time range', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({}); + expect(_g).toEqual({ + time: { + from: 'now-15m', + mode: 'relative', + to: 'now', + }, + }); + }); + + test('can specify query', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + query: { + language: 'kuery', + query: 'foo', + }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({ + query: { + language: 'kuery', + query: 'foo', + }, + }); + expect(_g).toEqual({}); + }); + + test('can specify local and global filters', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + filters: [ + { + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.APP_STATE, + }, + }, + { + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.GLOBAL_STATE, + }, + }, + ], + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({ + filters: [ + { + $state: { + store: 'appState', + }, + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + }, + ], + }); + expect(_g).toEqual({ + filters: [ + { + $state: { + store: 'globalState', + }, + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + }, + ], + }); + }); + + test('can set refresh interval', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + refreshInterval: { + pause: false, + value: 666, + }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({}); + expect(_g).toEqual({ + refreshInterval: { + pause: false, + value: 666, + }, + }); + }); + + test('can set time range', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + timeRange: { + from: 'now-3h', + to: 'now', + }, + }); + const { _a, _g } = getStatesFromKbnUrl(url, ['_a', '_g']); + + expect(_a).toEqual({}); + expect(_g).toEqual({ + time: { + from: 'now-3h', + to: 'now', + }, + }); + }); + + describe('useHash property', () => { + describe('when default useHash is set to false', () => { + test('when using default, sets index pattern ID in the generated URL', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(true); + }); + + test('when enabling useHash, does not set index pattern ID in the generated URL', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + useHash: true, + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(false); + }); + }); + + describe('when default useHash is set to true', () => { + test('when using default, does not set index pattern ID in the generated URL', async () => { + const { generator } = await setup({ useHash: true }); + const url = await generator.createUrl({ + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(false); + }); + + test('when disabling useHash, sets index pattern ID in the generated URL', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + useHash: false, + indexPatternId, + }); + + expect(url.indexOf(indexPatternId) > -1).toBe(true); + }); + }); + }); +}); diff --git a/src/plugins/discover/public/url_generator.ts b/src/plugins/discover/public/url_generator.ts new file mode 100644 index 00000000000000..42d689050d5ad4 --- /dev/null +++ b/src/plugins/discover/public/url_generator.ts @@ -0,0 +1,114 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + TimeRange, + Filter, + Query, + esFilters, + QueryState, + RefreshInterval, +} from '../../data/public'; +import { setStateToKbnUrl } from '../../kibana_utils/public'; +import { UrlGeneratorsDefinition } from '../../share/public'; + +export const DISCOVER_APP_URL_GENERATOR = 'DISCOVER_APP_URL_GENERATOR'; + +export interface DiscoverUrlGeneratorState { + /** + * Optionally set saved search ID. + */ + savedSearchId?: string; + + /** + * Optionally set index pattern ID. + */ + indexPatternId?: string; + + /** + * Optionally set the time range in the time picker. + */ + timeRange?: TimeRange; + + /** + * Optionally set the refresh interval. + */ + refreshInterval?: RefreshInterval; + + /** + * Optionally apply filers. + */ + filters?: Filter[]; + + /** + * Optionally set a query. NOTE: if given and used in conjunction with `dashboardId`, and the + * saved dashboard has a query saved with it, this will _replace_ that query. + */ + query?: Query; + + /** + * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines + * whether to hash the data in the url to avoid url length issues. + */ + useHash?: boolean; +} + +interface Params { + appBasePath: string; + useHash: boolean; +} + +export class DiscoverUrlGenerator + implements UrlGeneratorsDefinition { + constructor(private readonly params: Params) {} + + public readonly id = DISCOVER_APP_URL_GENERATOR; + + public readonly createUrl = async ({ + filters, + indexPatternId, + query, + refreshInterval, + savedSearchId, + timeRange, + useHash = this.params.useHash, + }: DiscoverUrlGeneratorState): Promise => { + const savedSearchPath = savedSearchId ? encodeURIComponent(savedSearchId) : ''; + const appState: { + query?: Query; + filters?: Filter[]; + index?: string; + } = {}; + const queryState: QueryState = {}; + + if (query) appState.query = query; + if (filters) appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); + if (indexPatternId) appState.index = indexPatternId; + + if (timeRange) queryState.time = timeRange; + if (filters) queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); + if (refreshInterval) queryState.refreshInterval = refreshInterval; + + let url = `${this.params.appBasePath}#/${savedSearchPath}`; + url = setStateToKbnUrl('_g', queryState, { useHash }, url); + url = setStateToKbnUrl('_a', appState, { useHash }, url); + + return url; + }; +} diff --git a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js index a0faf4a6a071c1..b3fbe8baadec37 100644 --- a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js +++ b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js @@ -94,11 +94,11 @@ export const createTopNavHelper = ({ TopNavMenu }) => (reactDirective) => { // All modifiers default to true. // Set to false to hide subcomponents. 'showSearchBar', - 'showFilterBar', 'showQueryBar', 'showQueryInput', - 'showDatePicker', 'showSaveQuery', + 'showDatePicker', + 'showFilterBar', 'appName', 'screenTitle', diff --git a/src/plugins/kibana_utils/public/core/create_start_service_getter.ts b/src/plugins/kibana_utils/public/core/create_start_service_getter.ts index 14e2588a0a9cf5..5e4a0f2bc7aeb0 100644 --- a/src/plugins/kibana_utils/public/core/create_start_service_getter.ts +++ b/src/plugins/kibana_utils/public/core/create_start_service_getter.ts @@ -19,16 +19,17 @@ import { CoreStart, StartServicesAccessor } from '../../../../core/public'; -export interface StartServices { +export interface StartServices { plugins: Plugins; self: OwnContract; - core: CoreStart; + core: Core; } -export type StartServicesGetter = () => StartServices< - Plugins, - OwnContract ->; +export type StartServicesGetter< + Plugins = unknown, + OwnContract = unknown, + Core = CoreStart +> = () => StartServices; /** * Use this utility to create a synchronous *start* service getter in *setup* diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index 74cfd125c2e3a0..46384fb3f27d5b 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -29,6 +29,7 @@ const dataShim = { }; describe('TopNavMenu', () => { + const WRAPPER_SELECTOR = '.kbnTopNavMenu__wrapper'; const TOP_NAV_ITEM_SELECTOR = 'TopNavMenuItem'; const SEARCH_BAR_SELECTOR = 'SearchBar'; const menuItems: TopNavMenuData[] = [ @@ -51,18 +52,28 @@ describe('TopNavMenu', () => { it('Should render nothing when no config is provided', () => { const component = shallowWithIntl(); + expect(component.find(WRAPPER_SELECTOR).length).toBe(0); + expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); + expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); + }); + + it('Should not render menu items when config is empty', () => { + const component = shallowWithIntl(); + expect(component.find(WRAPPER_SELECTOR).length).toBe(0); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); }); it('Should render 1 menu item', () => { const component = shallowWithIntl(); + expect(component.find(WRAPPER_SELECTOR).length).toBe(1); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(1); expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); }); it('Should render multiple menu items', () => { const component = shallowWithIntl(); + expect(component.find(WRAPPER_SELECTOR).length).toBe(1); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(menuItems.length); expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); }); @@ -71,15 +82,25 @@ describe('TopNavMenu', () => { const component = shallowWithIntl( ); - + expect(component.find(WRAPPER_SELECTOR).length).toBe(1); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(1); }); + it('Should render menu items and search bar', () => { + const component = shallowWithIntl( + + ); + expect(component.find(WRAPPER_SELECTOR).length).toBe(1); + expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(menuItems.length); + expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(1); + }); + it('Should render with a class name', () => { const component = shallowWithIntl( { return ( + {renderItems()} + + ); + } + + function renderSearchBar(): ReactElement | null { // Validate presense of all required fields - if (!showSearchBar || !props.data) return; + if (!showSearchBar || !props.data) return null; const { SearchBar } = props.data.ui; return ; } @@ -70,16 +95,7 @@ export function TopNavMenu(props: TopNavMenuProps) { const className = classNames('kbnTopNavMenu', props.className); return ( - - {renderItems()} - + {renderMenu(className)} {renderSearchBar()} ); diff --git a/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap b/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap index 8787e0c0273755..cae7aa96a7c0e3 100644 --- a/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap +++ b/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap @@ -44,7 +44,9 @@ exports[`share url panel content render 1`] = ` gutterSize="none" responsive={false} > - + - + - + - + - + - + - + - + `; + +exports[`should show url param extensions 1`] = ` + + + + } + labelType="label" + > + + + + + + + } + position="bottom" + /> + + , + }, + Object { + "data-test-subj": "exportAsSavedObject", + "disabled": false, + "id": "savedObject", + "label": + + + + + + } + position="bottom" + /> + + , + }, + ] + } + /> + + + + + + + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + + + + + + +`; diff --git a/src/plugins/share/public/components/share_context_menu.tsx b/src/plugins/share/public/components/share_context_menu.tsx index c12e9dabd1938d..26426853ddabe3 100644 --- a/src/plugins/share/public/components/share_context_menu.tsx +++ b/src/plugins/share/public/components/share_context_menu.tsx @@ -26,7 +26,7 @@ import { EuiContextMenu, EuiContextMenuPanelDescriptor } from '@elastic/eui'; import { HttpStart } from 'kibana/public'; import { UrlPanelContent } from './url_panel_content'; -import { ShareMenuItem, ShareContextMenuPanelItem } from '../types'; +import { ShareMenuItem, ShareContextMenuPanelItem, UrlParamExtension } from '../types'; interface Props { allowEmbed: boolean; @@ -39,6 +39,7 @@ interface Props { onClose: () => void; basePath: string; post: HttpStart['post']; + embedUrlParamExtensions?: UrlParamExtension[]; } export class ShareContextMenu extends Component { @@ -100,6 +101,7 @@ export class ShareContextMenu extends Component { basePath={this.props.basePath} post={this.props.post} shareableUrl={this.props.shareableUrl} + urlParamExtensions={this.props.embedUrlParamExtensions} /> ), }; diff --git a/src/plugins/share/public/components/url_panel_content.test.tsx b/src/plugins/share/public/components/url_panel_content.test.tsx index bd30dbf002df80..481f8312f4262a 100644 --- a/src/plugins/share/public/components/url_panel_content.test.tsx +++ b/src/plugins/share/public/components/url_panel_content.test.tsx @@ -202,3 +202,13 @@ describe('share url panel content', () => { }); }); }); + +test('should show url param extensions', () => { + const TestExtension = () =>
; + const extensions = [{ paramName: 'testExtension', component: TestExtension }]; + const component = shallow( + + ); + expect(component.find('TestExtension').length).toBe(1); + expect(component).toMatchSnapshot(); +}); diff --git a/src/plugins/share/public/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx index 2ece2052c4b958..65a8538693a495 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { Component } from 'react'; +import React, { Component, ReactElement } from 'react'; import { EuiButton, @@ -41,6 +41,7 @@ import { HttpStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { shortenUrl } from '../lib/url_shortener'; +import { UrlParamExtension } from '../types'; interface Props { allowShortUrl: boolean; @@ -50,6 +51,7 @@ interface Props { shareableUrl?: string; basePath: string; post: HttpStart['post']; + urlParamExtensions?: UrlParamExtension[]; } export enum ExportUrlAsType { @@ -57,12 +59,19 @@ export enum ExportUrlAsType { EXPORT_URL_AS_SNAPSHOT = 'snapshot', } +interface UrlParams { + [extensionName: string]: { + [queryParam: string]: boolean; + }; +} + interface State { exportUrlAs: ExportUrlAsType; useShortUrl: boolean; isCreatingShortUrl: boolean; url?: string; shortUrlErrorMsg?: string; + urlParams?: UrlParams; } export class UrlPanelContent extends Component { @@ -100,7 +109,7 @@ export class UrlPanelContent extends Component { {this.renderExportAsRadioGroup()} - + {this.renderUrlParamExtensions()} {this.renderShortUrlSwitch()} @@ -151,6 +160,13 @@ export class UrlPanelContent extends Component { } }; + private updateUrlParams = (url: string) => { + const embedUrl = this.props.isEmbedded ? this.makeUrlEmbeddable(url) : url; + const extendUrl = this.state.urlParams ? this.getUrlParamExtensions(embedUrl) : embedUrl; + + return extendUrl; + }; + private getSavedObjectUrl = () => { if (this.isNotSaved()) { return; @@ -166,7 +182,7 @@ export class UrlPanelContent extends Component { // Get the application route, after the hash, and remove the #. const parsedAppUrl = parseUrl(parsedUrl.hash.slice(1), true); - let formattedUrl = formatUrl({ + const formattedUrl = formatUrl({ protocol: parsedUrl.protocol, auth: parsedUrl.auth, host: parsedUrl.host, @@ -180,28 +196,42 @@ export class UrlPanelContent extends Component { }, }), }); - if (this.props.isEmbedded) { - formattedUrl = this.makeUrlEmbeddable(formattedUrl); - } - return formattedUrl; + return this.updateUrlParams(formattedUrl); }; private getSnapshotUrl = () => { - let url = this.props.shareableUrl || window.location.href; - if (this.props.isEmbedded) { - url = this.makeUrlEmbeddable(url); - } - return url; + const url = this.props.shareableUrl || window.location.href; + + return this.updateUrlParams(url); }; - private makeUrlEmbeddable = (url: string) => { - const embedQueryParam = '?embed=true'; + private makeUrlEmbeddable = (url: string): string => { + const embedParam = '?embed=true'; const urlHasQueryString = url.indexOf('?') !== -1; + if (urlHasQueryString) { - return url.replace('?', `${embedQueryParam}&`); + return url.replace('?', `${embedParam}&`); } - return `${url}${embedQueryParam}`; + + return `${url}${embedParam}`; + }; + + private getUrlParamExtensions = (url: string): string => { + const { urlParams } = this.state; + return urlParams + ? Object.keys(urlParams).reduce((urlAccumulator, key) => { + const urlParam = urlParams[key]; + return urlParam + ? Object.keys(urlParam).reduce((queryAccumulator, queryParam) => { + const isQueryParamEnabled = urlParam[queryParam]; + return isQueryParamEnabled + ? queryAccumulator + `&${queryParam}=true` + : queryAccumulator; + }, urlAccumulator) + : urlAccumulator; + }, url) + : url; }; private makeIframeTag = (url?: string) => { @@ -247,6 +277,10 @@ export class UrlPanelContent extends Component { } // "Use short URL" is checked but shortUrl has not been generated yet so one needs to be created. + this.createShortUrl(); + }; + + private createShortUrl = async () => { this.setState({ isCreatingShortUrl: true, shortUrlErrorMsg: undefined, @@ -262,7 +296,7 @@ export class UrlPanelContent extends Component { this.setState( { isCreatingShortUrl: false, - useShortUrl: isChecked, + useShortUrl: true, }, this.setUrl ); @@ -321,7 +355,7 @@ export class UrlPanelContent extends Component { private renderWithIconTip = (child: React.ReactNode, tipContent: React.ReactNode) => { return ( - {child} + {child} @@ -397,4 +431,34 @@ export class UrlPanelContent extends Component { ); }; + + private renderUrlParamExtensions = (): ReactElement | void => { + if (!this.props.urlParamExtensions) { + return; + } + + const setParamValue = (paramName: string) => ( + values: { [queryParam: string]: boolean } = {} + ): void => { + const stateUpdate = { + urlParams: { + ...this.state.urlParams, + [paramName]: { + ...values, + }, + }, + }; + this.setState(stateUpdate, this.state.useShortUrl ? this.createShortUrl : this.setUrl); + }; + + return ( + + {this.props.urlParamExtensions.map(({ paramName, component: UrlParamComponent }) => ( + + + + ))} + + ); + }; } diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index 35116efa859615..3325c5503fe894 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -67,6 +67,7 @@ export class ShareMenuManager { shareableUrl, post, basePath, + embedUrlParamExtensions, }: ShowShareMenuOptions & { menuItems: ShareMenuItem[]; post: HttpStart['post']; @@ -102,6 +103,7 @@ export class ShareMenuManager { onClose={this.onClose} post={post} basePath={basePath} + embedUrlParamExtensions={embedUrlParamExtensions} /> diff --git a/src/plugins/share/public/types.ts b/src/plugins/share/public/types.ts index 6b20f1f53a28c7..8dda9f1195a390 100644 --- a/src/plugins/share/public/types.ts +++ b/src/plugins/share/public/types.ts @@ -17,6 +17,7 @@ * under the License. */ +import { ComponentType } from 'react'; import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; /** @@ -80,9 +81,19 @@ export interface ShareMenuProvider { getShareMenuItems: (context: ShareContext) => ShareMenuItem[]; } +interface UrlParamExtensionProps { + setParamValue: (values: {}) => void; +} + +export interface UrlParamExtension { + paramName: string; + component: ComponentType; +} + /** @public */ export interface ShowShareMenuOptions extends Omit { anchorElement: HTMLElement; allowEmbed: boolean; allowShortUrl: boolean; + embedUrlParamExtensions?: UrlParamExtension[]; } diff --git a/src/plugins/share/public/url_generators/url_generator_service.ts b/src/plugins/share/public/url_generators/url_generator_service.ts index 13c1b94acdd07e..b63e2a45d6812c 100644 --- a/src/plugins/share/public/url_generators/url_generator_service.ts +++ b/src/plugins/share/public/url_generators/url_generator_service.ts @@ -24,7 +24,7 @@ import { UrlGeneratorInternal } from './url_generator_internal'; import { UrlGeneratorContract } from './url_generator_contract'; export interface UrlGeneratorsStart { - getUrlGenerator: (urlGeneratorId: UrlGeneratorId) => UrlGeneratorContract; + getUrlGenerator: (urlGeneratorId: T) => UrlGeneratorContract; } export interface UrlGeneratorsSetup { diff --git a/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx b/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx index a316a087c8bcbc..ae3da8e203a57d 100644 --- a/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx @@ -38,6 +38,7 @@ function HasExtendedBoundsParamEditor(props: AggParamEditorProps) { setValue(value && agg.params.min_doc_count); } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [agg.params.min_doc_count, setValue, value]); return ( diff --git a/src/plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts b/src/plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts index ee24e2b42113da..950c8563492308 100644 --- a/src/plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts +++ b/src/plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts @@ -33,6 +33,7 @@ const CUSTOM_METRIC = { }; function useCompatibleAggCallback(aggFilter: AggFilter) { + /* eslint-disable-next-line react-hooks/exhaustive-deps */ return useCallback(isCompatibleAggregation(aggFilter), [aggFilter]); } diff --git a/src/plugins/vis_type_timelion/public/components/panel.tsx b/src/plugins/vis_type_timelion/public/components/panel.tsx index 4c28e4e5a18abb..99c5532c04832b 100644 --- a/src/plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/plugins/vis_type_timelion/public/components/panel.tsx @@ -102,6 +102,7 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { [chartElem] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const highlightSeries = useCallback( debounce(({ currentTarget }: JQuery.TriggeredEvent) => { const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); @@ -295,6 +296,7 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { [plot, legendValueNumbers, unhighlightSeries, legendCaption] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const debouncedSetLegendNumbers = useCallback( debounce(setLegendNumbers, DEBOUNCE_DELAY, { maxWait: DEBOUNCE_DELAY, diff --git a/test/functional/apps/context/_filters.js b/test/functional/apps/context/_filters.js index 470ef462b9d9d2..66888d441954e5 100644 --- a/test/functional/apps/context/_filters.js +++ b/test/functional/apps/context/_filters.js @@ -17,8 +17,6 @@ * under the License. */ -import expect from '@kbn/expect'; - const TEST_INDEX_PATTERN = 'logstash-*'; const TEST_ANCHOR_ID = 'AU_x3_BrGFA8no6QjjaI'; const TEST_ANCHOR_FILTER_FIELD = 'geo.src'; @@ -40,20 +38,19 @@ export default function ({ getService, getPageObjects }) { }); it('inclusive filter should be addable via expanded doc table rows', async function () { - await docTable.toggleRowExpanded({ isAnchorRow: true }); - - await retry.try(async () => { + await retry.waitFor(`filter ${TEST_ANCHOR_FILTER_FIELD} in filterbar`, async () => { + await docTable.toggleRowExpanded({ isAnchorRow: true }); const anchorDetailsRow = await docTable.getAnchorDetailsRow(); await docTable.addInclusiveFilter(anchorDetailsRow, TEST_ANCHOR_FILTER_FIELD); await PageObjects.context.waitUntilContextLoadingHasFinished(); - expect( - await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, true) - ).to.be(true); + + return await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, true); + }); + await retry.waitFor(`filter matching docs in docTable`, async () => { const fields = await docTable.getFields(); - const hasOnlyFilteredRows = fields + return fields .map((row) => row[2]) .every((fieldContent) => fieldContent === TEST_ANCHOR_FILTER_VALUE); - expect(hasOnlyFilteredRows).to.be(true); }); }); @@ -64,26 +61,27 @@ export default function ({ getService, getPageObjects }) { await filterBar.toggleFilterEnabled(TEST_ANCHOR_FILTER_FIELD); await PageObjects.context.waitUntilContextLoadingHasFinished(); - await retry.try(async () => { - expect( - await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, false) - ).to.be(true); + await retry.waitFor(`a disabled filter in filterbar`, async () => { + return await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, false); + }); + + await retry.waitFor('filters are disabled', async () => { const fields = await docTable.getFields(); const hasOnlyFilteredRows = fields .map((row) => row[2]) .every((fieldContent) => fieldContent === TEST_ANCHOR_FILTER_VALUE); - expect(hasOnlyFilteredRows).to.be(false); + return hasOnlyFilteredRows === false; }); }); it('filter for presence should be addable via expanded doc table rows', async function () { await docTable.toggleRowExpanded({ isAnchorRow: true }); - await retry.try(async () => { + await retry.waitFor('an exists filter in the filterbar', async () => { const anchorDetailsRow = await docTable.getAnchorDetailsRow(); await docTable.addExistsFilter(anchorDetailsRow, TEST_ANCHOR_FILTER_FIELD); await PageObjects.context.waitUntilContextLoadingHasFinished(); - expect(await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, 'exists', true)).to.be(true); + return await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, 'exists', true); }); }); }); diff --git a/test/functional/apps/context/_size.js b/test/functional/apps/context/_size.js index 3beb070b50deb1..067a23daacb4a6 100644 --- a/test/functional/apps/context/_size.js +++ b/test/functional/apps/context/_size.js @@ -16,69 +16,69 @@ * specific language governing permissions and limitations * under the License. */ - -import expect from '@kbn/expect'; - const TEST_INDEX_PATTERN = 'logstash-*'; const TEST_ANCHOR_ID = 'AU_x3_BrGFA8no6QjjaI'; -const TEST_DEFAULT_CONTEXT_SIZE = 7; -const TEST_STEP_SIZE = 3; +const TEST_DEFAULT_CONTEXT_SIZE = 2; +const TEST_STEP_SIZE = 2; export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); const retry = getService('retry'); const docTable = getService('docTable'); const PageObjects = getPageObjects(['context']); + let expectedRowLength = 2 * TEST_DEFAULT_CONTEXT_SIZE + 1; - // FLAKY: https://github.com/elastic/kibana/issues/53888 - describe.skip('context size', function contextSize() { + describe('context size', function contextSize() { before(async function () { await kibanaServer.uiSettings.update({ 'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`, 'context:step': `${TEST_STEP_SIZE}`, }); + await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID); }); it('should default to the `context:defaultSize` setting', async function () { - await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID); - - await retry.try(async function () { - expect(await docTable.getRowsText()).to.have.length(2 * TEST_DEFAULT_CONTEXT_SIZE + 1); - }); - await retry.try(async function () { - const predecessorCountPicker = await PageObjects.context.getPredecessorCountPicker(); - expect(await predecessorCountPicker.getAttribute('value')).to.equal( - `${TEST_DEFAULT_CONTEXT_SIZE}` - ); - }); - await retry.try(async function () { - const successorCountPicker = await PageObjects.context.getSuccessorCountPicker(); - expect(await successorCountPicker.getAttribute('value')).to.equal( - `${TEST_DEFAULT_CONTEXT_SIZE}` - ); - }); + await retry.waitFor( + `number of rows displayed initially is ${expectedRowLength}`, + async function () { + const rows = await docTable.getRowsText(); + return rows.length === expectedRowLength; + } + ); + await retry.waitFor( + `predecessor count picker is set to ${TEST_DEFAULT_CONTEXT_SIZE}`, + async function () { + const predecessorCountPicker = await PageObjects.context.getPredecessorCountPicker(); + const value = await predecessorCountPicker.getAttribute('value'); + return value === String(TEST_DEFAULT_CONTEXT_SIZE); + } + ); }); it('should increase according to the `context:step` setting when clicking the `load newer` button', async function () { - await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID); await PageObjects.context.clickPredecessorLoadMoreButton(); + expectedRowLength += TEST_STEP_SIZE; - await retry.try(async function () { - expect(await docTable.getRowsText()).to.have.length( - 2 * TEST_DEFAULT_CONTEXT_SIZE + TEST_STEP_SIZE + 1 - ); - }); + await retry.waitFor( + `number of rows displayed after clicking load more predecessors is ${expectedRowLength}`, + async function () { + const rows = await docTable.getRowsText(); + return rows.length === expectedRowLength; + } + ); }); it('should increase according to the `context:step` setting when clicking the `load older` button', async function () { - await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_ID); await PageObjects.context.clickSuccessorLoadMoreButton(); + expectedRowLength += TEST_STEP_SIZE; - await retry.try(async function () { - expect(await docTable.getRowsText()).to.have.length( - 2 * TEST_DEFAULT_CONTEXT_SIZE + TEST_STEP_SIZE + 1 - ); - }); + await retry.waitFor( + `number of rows displayed after clicking load more successors is ${expectedRowLength}`, + async function () { + const rows = await docTable.getRowsText(); + return rows.length === expectedRowLength; + } + ); }); }); } diff --git a/test/functional/apps/dashboard/embed_mode.js b/test/functional/apps/dashboard/embed_mode.js index 65ef75f3f65e1f..a1828143555b01 100644 --- a/test/functional/apps/dashboard/embed_mode.js +++ b/test/functional/apps/dashboard/embed_mode.js @@ -20,6 +20,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { + const testSubjects = getService('testSubjects'); const retry = getService('retry'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); @@ -28,6 +29,13 @@ export default function ({ getService, getPageObjects }) { const globalNav = getService('globalNav'); describe('embed mode', () => { + const urlParamExtensions = [ + 'show-top-menu=true', + 'show-query-input=true', + 'show-time-filter=true', + 'hide-filter-bar=true', + ]; + before(async () => { await esArchiver.load('dashboard/current/kibana'); await kibanaServer.uiSettings.replace({ @@ -54,9 +62,28 @@ export default function ({ getService, getPageObjects }) { }); }); + it('shows or hides elements based on URL params', async () => { + await testSubjects.missingOrFail('top-nav'); + await testSubjects.missingOrFail('queryInput'); + await testSubjects.missingOrFail('superDatePickerToggleQuickMenuButton'); + await testSubjects.existOrFail('showFilterActions'); + + const currentUrl = await browser.getCurrentUrl(); + const newUrl = [currentUrl].concat(urlParamExtensions).join('&'); + // Embed parameter only works on a hard refresh. + const useTimeStamp = true; + await browser.get(newUrl.toString(), useTimeStamp); + + await testSubjects.existOrFail('top-nav'); + await testSubjects.existOrFail('queryInput'); + await testSubjects.existOrFail('superDatePickerToggleQuickMenuButton'); + await testSubjects.missingOrFail('showFilterActions'); + }); + after(async function () { const currentUrl = await browser.getCurrentUrl(); - const newUrl = currentUrl.replace('&embed=true', ''); + const replaceParams = ['', 'embed=true'].concat(urlParamExtensions).join('&'); + const newUrl = currentUrl.replace(replaceParams, ''); // First use the timestamp to cause a hard refresh so the new embed parameter works correctly. let useTimeStamp = true; await browser.get(newUrl.toString(), useTimeStamp); diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 68a5ad5ba96ab9..8cdb9883ea5a0f 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -67,17 +67,17 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo * @param appUrl Kibana URL */ private async loginIfPrompted(appUrl: string, insertTimestamp: boolean) { + // Disable the welcome screen. This is relevant for environments + // which don't allow to use the yml setting, e.g. cloud production. + // It is done here so it applies to logins but also to a login re-use. + await browser.setLocalStorageItem('home:welcome:show', 'false'); + let currentUrl = await browser.getCurrentUrl(); log.debug(`currentUrl = ${currentUrl}\n appUrl = ${appUrl}`); await testSubjects.find('kibanaChrome', 6 * defaultFindTimeout); // 60 sec waiting const loginPage = currentUrl.includes('/login'); const wantedLoginPage = appUrl.includes('/login') || appUrl.includes('/logout'); - // Disable the welcome screen. This is relevant for environments - // which don't allow to use the yml setting, e.g. cloud production. - // It is done here so it applies to logins but also to a login re-use. - await browser.setLocalStorageItem('home:welcome:show', 'false'); - if (loginPage && !wantedLoginPage) { log.debug('Found login page'); if (config.get('security.disableTestUser')) { diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts index 6f4e1f052f5e09..21b12e2134767a 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/app/dashboard_input.ts @@ -92,6 +92,7 @@ export const dashboardInput: DashboardContainerInput = { }, }, }, + isEmbeddedExternally: false, isFullScreenMode: false, filters: [], useMargins: true, diff --git a/test/scripts/jenkins_xpack_page_load_metrics.sh b/test/scripts/jenkins_xpack_page_load_metrics.sh new file mode 100644 index 00000000000000..679f0b8d2ddc59 --- /dev/null +++ b/test/scripts/jenkins_xpack_page_load_metrics.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +source test/scripts/jenkins_test_setup_xpack.sh + +checks-reporter-with-killswitch "Capture Kibana page load metrics" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$installDir" \ + --config test/page_load_metrics/config.ts; diff --git a/x-pack/.gitignore b/x-pack/.gitignore index 305032926579b6..68262c4bf734ba 100644 --- a/x-pack/.gitignore +++ b/x-pack/.gitignore @@ -3,6 +3,7 @@ /target /test/functional/failure_debug /test/functional/screenshots +/test/page_load_metrics/screenshots /test/functional/apps/reporting/reports/session /test/reporting/configs/failure_debug/ /legacy/plugins/reporting/.chromium/ diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index d17e5d1a74a300..85b40d33c40896 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -48,7 +48,8 @@ "xpack.triggersActionsUI": "plugins/triggers_actions_ui", "xpack.upgradeAssistant": "plugins/upgrade_assistant", "xpack.uptime": ["plugins/uptime"], - "xpack.watcher": "plugins/watcher" + "xpack.watcher": "plugins/watcher", + "xpack.observability": "plugins/observability" }, "translations": [ "plugins/translations/translations/zh-CN.json", diff --git a/x-pack/examples/ui_actions_enhanced_examples/kibana.json b/x-pack/examples/ui_actions_enhanced_examples/kibana.json index 7f3ed80ed62fac..a1cd895bb3cd62 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/kibana.json +++ b/x-pack/examples/ui_actions_enhanced_examples/kibana.json @@ -5,6 +5,6 @@ "configPath": ["ui_actions_enhanced_examples"], "server": false, "ui": true, - "requiredPlugins": ["uiActionsEnhanced", "data"], + "requiredPlugins": ["uiActionsEnhanced", "data", "discover"], "optionalPlugins": [] } diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx index 0237e128c5a2fc..da9b0e921fb1cb 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx @@ -31,9 +31,7 @@ export const DiscoverDrilldownConfig: React.FC = ( onIndexPatternSelect, customIndexPattern, onCustomIndexPatternToggle, - carryFiltersAndQuery, onCarryFiltersAndQueryToggle, - carryTimeRange, onCarryTimeRangeToggle, }) => { return ( @@ -82,9 +80,10 @@ export const DiscoverDrilldownConfig: React.FC = ( {!!onCarryFiltersAndQueryToggle && ( @@ -92,9 +91,10 @@ export const DiscoverDrilldownConfig: React.FC = ( {!!onCarryTimeRangeToggle && ( diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx index 91e05d16b43629..ba88f49861ffef 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx @@ -22,7 +22,7 @@ const isOutputWithIndexPatterns = ( }; export interface Params { - start: StartServicesGetter>; + start: StartServicesGetter>; } export class DashboardToDiscoverDrilldown implements Drilldown { @@ -54,6 +54,10 @@ export class DashboardToDiscoverDrilldown implements Drilldown => { + const { urlGenerator } = this.params.start().plugins.discover; + + if (!urlGenerator) throw new Error('Discover URL generator not available.'); + let indexPatternId = !!config.customIndexPattern && !!config.indexPatternId ? config.indexPatternId : ''; @@ -64,8 +68,9 @@ export class DashboardToDiscoverDrilldown implements Drilldown => { diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts index 3f28854351f556..8034c378cc64f7 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts @@ -14,14 +14,17 @@ import { DashboardHelloWorldDrilldown } from './dashboard_hello_world_drilldown' import { DashboardToUrlDrilldown } from './dashboard_to_url_drilldown'; import { DashboardToDiscoverDrilldown } from './dashboard_to_discover_drilldown'; import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; +import { DiscoverSetup, DiscoverStart } from '../../../../src/plugins/discover/public'; export interface SetupDependencies { data: DataPublicPluginSetup; + discover: DiscoverSetup; uiActionsEnhanced: AdvancedUiActionsSetup; } export interface StartDependencies { data: DataPublicPluginStart; + discover: DiscoverStart; uiActionsEnhanced: AdvancedUiActionsStart; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/case/types.ts b/x-pack/plugins/actions/server/builtin_action_types/case/types.ts index 459e9d2b03f926..992b2cb16fb06a 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/case/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/case/types.ts @@ -21,6 +21,7 @@ import { ExecutorSubActionGetIncidentParamsSchema, ExecutorSubActionHandshakeParamsSchema, } from './schema'; +import { LicenseType } from '../../../../../legacy/common/constants'; export interface AnyParams { [index: string]: string | number | object | undefined | null; @@ -51,6 +52,7 @@ export type Comment = TypeOf; export interface ExternalServiceConfiguration { id: string; name: string; + minimumLicenseRequired: LicenseType; } export interface ExternalServiceCredentials { diff --git a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts index 315d13b5aa7733..dd8d971b7df44f 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts @@ -120,9 +120,7 @@ export const createConnector = ({ configurationUtilities, executor = createConnectorExecutor({ api, createExternalService }), }: CreateActionTypeArgs): ActionType => ({ - id: config.id, - name: config.name, - minimumLicenseRequired: 'platinum', + ...config, validate: { config: schema.object(validationSchema.config, { validate: curry(validate.config)(configurationUtilities), diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/config.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/config.ts index 7e415109f1bd9d..54f28e447010a7 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/config.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/config.ts @@ -10,4 +10,5 @@ import * as i18n from './translations'; export const config: ExternalServiceConfiguration = { id: '.jira', name: i18n.NAME, + minimumLicenseRequired: 'gold', }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts index 4ad8108c3b1374..70d53ab79f6310 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts @@ -10,4 +10,5 @@ import * as i18n from './translations'; export const config: ExternalServiceConfiguration = { id: '.servicenow', name: i18n.NAME, + minimumLicenseRequired: 'platinum', }; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx index 5bb678d1c08afc..50eb85715969a0 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx @@ -79,6 +79,7 @@ export function AgentConfigurationCreateEdit({ ..._newConfig, settings: existingConfig?.settings || {}, })); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [existingConfig]); // update newConfig when existingConfig has loaded diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index d39ad530c1b4ca..1244dd01a3b439 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -109,10 +109,12 @@ export const TransactionDistribution: FunctionComponent = ( bucketIndex, } = props; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const formatYShort = useCallback(getFormatYShort(transactionType), [ transactionType, ]); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const formatYLong = useCallback(getFormatYLong(transactionType), [ transactionType, ]); diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 988edb197a2304..2507eca9ff6638 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -66,6 +66,7 @@ export const TransactionActionMenu: FunctionComponent = ({ { key: 'transaction.name', value: transaction?.transaction.name }, { key: 'transaction.type', value: transaction?.transaction.type }, ].filter((filter): filter is Filter => typeof filter.value === 'string'), + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [transaction] ); diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx b/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx index 28b836cd2c6506..2db4659c836036 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx @@ -106,6 +106,7 @@ describe('useFetcher', () => { jest.useFakeTimers(); const hook = renderHook( + /* eslint-disable-next-line react-hooks/exhaustive-deps */ ({ callback, args }) => useFetcher(callback, args), { initialProps: { @@ -165,6 +166,7 @@ describe('useFetcher', () => { it('should return the same object reference when data is unchanged between rerenders', async () => { const hook = renderHook( + /* eslint-disable-next-line react-hooks/exhaustive-deps */ ({ callback, args }) => useFetcher(callback, args), { initialProps: { diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 76320efe617eae..0939c51b166050 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -75,7 +75,7 @@ export class ApmPlugin implements Plugin { core.application.register({ id: 'apm', title: 'APM', - order: 8100, + order: 8300, euiIconType: 'apmApp', appRoute: '/app/apm', icon: 'plugins/apm/public/icon.svg', diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx index 52033a00327c0d..d26575f65dfec5 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx @@ -41,6 +41,7 @@ export const MetricsAlertDropdown = () => { , ]; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [kibana.services]); return ( diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index db5cfb1416dec3..7a71bb68bc54f4 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -90,6 +90,7 @@ export const Expressions: React.FC = (props) => { aggregation: 'avg', }; } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [alertsContext.metadata]); const updateParams = useCallback( @@ -109,6 +110,7 @@ export const Expressions: React.FC = (props) => { timeUnit: timeUnit ?? defaultExpression.timeUnit, }); setAlertParams('criteria', exp); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [setAlertParams, alertParams.criteria, timeSize, timeUnit]); const removeExpression = useCallback( @@ -119,6 +121,7 @@ export const Expressions: React.FC = (props) => { setAlertParams('criteria', exp); } }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [setAlertParams, alertParams.criteria] ); @@ -133,6 +136,7 @@ export const Expressions: React.FC = (props) => { [setAlertParams, derivedIndexPattern] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [ onFilterChange, ]); @@ -162,6 +166,7 @@ export const Expressions: React.FC = (props) => { setTimeSize(ts || undefined); setAlertParams('criteria', criteria); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [alertParams.criteria, setAlertParams] ); @@ -175,6 +180,7 @@ export const Expressions: React.FC = (props) => { setTimeUnit(tu as TimeUnit); setAlertParams('criteria', criteria); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [alertParams.criteria, setAlertParams] ); diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx index 64a5792689d520..a886dccf105cfb 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx @@ -90,6 +90,7 @@ export const ExpressionChart: React.FC = ({ : (value: number) => `${value}`; }, [data]); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const yAxisFormater = useCallback(createFormatterForMetric(metric), [expression]); if (loading || !data) { diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx index c48b5b9a2cc58a..47a0f037816bc1 100644 --- a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx @@ -41,6 +41,7 @@ export const InventoryAlertDropdown = () => { , ]; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [kibana.services]); return ( diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx index f4fab113cdd171..074464fb55414a 100644 --- a/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx @@ -117,6 +117,7 @@ export const Expressions: React.FC = (props) => { timeUnit: timeUnit ?? defaultExpression.timeUnit, }); setAlertParams('criteria', exp); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [setAlertParams, alertParams.criteria, timeSize, timeUnit]); const removeExpression = useCallback( @@ -141,6 +142,7 @@ export const Expressions: React.FC = (props) => { [derivedIndexPattern, setAlertParams] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [ onFilterChange, ]); diff --git a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx index d81d11e01d4a5a..609f99805fe9c0 100644 --- a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx @@ -137,6 +137,7 @@ export const Editor: React.FC = (props) => { } else { return []; } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [sourceStatus]); const updateCount = useCallback( @@ -176,6 +177,7 @@ export const Editor: React.FC = (props) => { ? [...alertParams.criteria, DEFAULT_CRITERIA] : [DEFAULT_CRITERIA]; setAlertParams('criteria', nextCriteria); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [alertParams, setAlertParams]); const removeCriterion = useCallback( @@ -185,6 +187,7 @@ export const Editor: React.FC = (props) => { }); setAlertParams('criteria', nextCriteria); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [alertParams, setAlertParams] ); diff --git a/x-pack/plugins/infra/public/components/header/header.tsx b/x-pack/plugins/infra/public/components/header/header.tsx index fa71426f836459..47ee1857da591d 100644 --- a/x-pack/plugins/infra/public/components/header/header.tsx +++ b/x-pack/plugins/infra/public/components/header/header.tsx @@ -31,10 +31,12 @@ export const Header = ({ breadcrumbs = [], readOnlyBadge = false }: HeaderProps) const setBreadcrumbs = useCallback(() => { return chrome?.setBreadcrumbs(breadcrumbs || []); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [breadcrumbs, chrome]); const setBadge = useCallback(() => { return chrome?.setBadge(badge); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [badge, chrome]); useEffect(() => { diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts index 5fe9a45a7ceede..d5b2a0aaa61c01 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts @@ -269,6 +269,7 @@ const useFetchEntriesEffect = ( } }; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const fetchNewerEntries = useCallback( throttle(() => runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After), 500), [props, state.bottomCursor] @@ -330,10 +331,12 @@ const useFetchEntriesEffect = ( props.timestampsLastUpdate, ]; + /* eslint-disable react-hooks/exhaustive-deps */ useEffect(fetchNewEntriesEffect, fetchNewEntriesEffectDependencies); useEffect(fetchMoreEntriesEffect, fetchMoreEntriesEffectDependencies); useEffect(streamEntriesEffect, streamEntriesEffectDependencies); useEffect(expandRangeEffect, expandRangeEffectDependencies); + /* eslint-enable react-hooks/exhaustive-deps */ return { fetchNewerEntries, checkForNewEntries: runFetchNewEntriesRequest }; }; diff --git a/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts b/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts index 7c903f59002dc5..d5a43c0d6cffae 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts @@ -82,6 +82,7 @@ export const useLogFilterState: (props: { } return true; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [filterQueryDraft]); const serializedFilterQuery = useMemo(() => (filterQuery ? filterQuery.serializedQuery : null), [ diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts index 670988d6801471..80aab6237518f9 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts @@ -78,6 +78,7 @@ export const useLogSource = ({ [sourceId, fetch] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const logIndicesExist = useMemo(() => (sourceStatus?.logIndexNames?.length ?? 0) > 0, [ sourceStatus, ]); @@ -87,6 +88,7 @@ export const useLogSource = ({ fields: sourceStatus?.logIndexFields ?? [], title: sourceConfiguration?.configuration.name ?? 'unknown', }), + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [sourceConfiguration, sourceStatus] ); diff --git a/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts b/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts index 94e2537a67a2ac..54d565d9ee223c 100644 --- a/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts +++ b/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts @@ -76,6 +76,7 @@ export const useSourceViaHttp = ({ title: pickIndexPattern(response?.source, indexType), }; }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [response, type] ); diff --git a/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx index 2a70edc9b9a57b..cfa9a711f7743b 100644 --- a/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx @@ -35,6 +35,7 @@ export const useBulkGetSavedObject = (type: string) => { }; fetchData(); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx index 8313d496a06518..0efb862ad2eb46 100644 --- a/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx @@ -40,6 +40,7 @@ export const useCreateSavedObject = (type: string) => { }; save(); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx index 3f2d15b3b86aa6..e353a79b190739 100644 --- a/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx @@ -29,6 +29,7 @@ export const useDeleteSavedObject = (type: string) => { }; dobj(); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx index 8b0ab45f6e6d10..8eb6db6103ed8c 100644 --- a/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx @@ -37,6 +37,7 @@ export const useFindSavedObject = { title: loadDataErrorTitle, }); }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [services.notifications] ); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx index 156c9a919440e2..3c8db3f8246c0c 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx @@ -127,6 +127,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => { [setAutoRefresh] ); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const hasResults = useMemo(() => (logEntryRate?.histogramBuckets?.length ?? 0) > 0, [ logEntryRate, ]); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx index 11ea137c95a1f2..a1d3d56beee2cb 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx @@ -29,6 +29,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ const logEntryRateSeries = useMemo( () => results?.histogramBuckets ? getLogEntryRateSeriesForPartition(results, partitionId) : [], + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [results, partitionId] ); const anomalyAnnotations = useMemo( @@ -41,6 +42,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ major: [], critical: [], }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [results, partitionId] ); const totalNumberOfLogEntries = useMemo( @@ -48,6 +50,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ results?.histogramBuckets ? getTotalNumberOfLogEntriesForPartition(results, partitionId) : undefined, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [results, partitionId] ); return ( diff --git a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx index 34c4202ab8b653..f41158e114c7df 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx @@ -42,6 +42,7 @@ export const LogsSettingsPage = () => { const availableFields = useMemo( () => sourceStatus?.logIndexFields.map((field) => field.name) ?? [], + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [sourceStatus] ); diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx index b6e6710a0b3b47..cf3eae263ed590 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx @@ -28,6 +28,7 @@ const MODAL_MARGIN = 25; export const PageViewLogInContext: React.FC = () => { const { sourceConfiguration } = useLogSourceContext(); const { textScale, textWrap } = useContext(LogViewConfiguration.Context); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const columnConfigurations = useMemo(() => sourceConfiguration?.configuration.logColumns ?? [], [ sourceConfiguration, ]); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 8b5b191ccfdd98..1452772e49ca15 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -76,6 +76,7 @@ export const Layout = () => { const intervalAsString = convertIntervalToString(interval); const dataBounds = calculateBoundsFromNodes(nodes); const bounds = autoBounds ? dataBounds : boundsOverride; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const formatter = useCallback(createInventoryMetricFormatter(options.metric), [options.metric]); return ( diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/chart_section_vis.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/chart_section_vis.tsx index 6a4d6521855a93..dee2e0c9f457f0 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/chart_section_vis.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/chart_section_vis.tsx @@ -45,6 +45,7 @@ export const ChartSectionVis = ({ }: VisSectionProps) => { const isDarkMode = useUiSetting('theme:darkMode'); const [dateFormat] = useKibanaUiSetting('dateFormat'); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const valueFormatter = useCallback(getFormatter(formatter, formatterTemplate), [ formatter, formatterTemplate, diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx index 4c75003616117d..88e7c0c08e4418 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx @@ -23,6 +23,7 @@ export const SubSection: FunctionComponent = ({ isLiveStreaming, stopLiveStreaming, }) => { + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const metric = useMemo(() => metrics?.find((m) => m.id === id), [id, metrics]); if (!children || !metric) { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx index e9e2ca4d9682ad..aafca12b0eff21 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx @@ -86,6 +86,7 @@ export const MetricsExplorerChart = ({ [dateFormat] ), }; + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const yAxisFormater = useCallback(createFormatterForMetric(first(metrics)), [options]); const dataDomain = calculateDomain(series, metrics, chartOptions.stack); const domain = diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index ead5644d19fa29..deae78e22c6a11 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -64,7 +64,7 @@ export class Plugin defaultMessage: 'Logs', }), euiIconType: 'logsApp', - order: 8000, + order: 8100, appRoute: '/app/logs', category: DEFAULT_APP_CATEGORIES.observability, mount: async (params: AppMountParameters) => { @@ -89,7 +89,7 @@ export class Plugin defaultMessage: 'Metrics', }), euiIconType: 'metricsApp', - order: 8001, + order: 8200, appRoute: '/app/metrics', category: DEFAULT_APP_CATEGORIES.observability, mount: async (params: AppMountParameters) => { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index 1dd7e660deaa90..6fab78951038fa 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -169,6 +169,7 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { ))} ), + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [agentConfig, configId, agentStatus] ); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx index cdc4f1c63a11dd..057970aa1ee926 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx @@ -48,6 +48,7 @@ export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastre items: [ { icon: 'dashboardApp', + /* eslint-disable-next-line react-hooks/rules-of-hooks */ href: useKibanaLink(`/dashboard/${dashboards[0].id || ''}`), name: actionNameSingular, }, @@ -70,6 +71,7 @@ export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastre items: dashboards.map((dashboard) => { return { icon: 'dashboardApp', + /* eslint-disable-next-line react-hooks/rules-of-hooks */ href: useKibanaLink(`/dashboard/${dashboard.id || ''}`), name: dashboard.title, }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx index 1a7681584ff157..5bb0464801f70e 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx @@ -90,6 +90,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => { ), + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [agentData, agentId, getHref] ); @@ -141,6 +142,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => { ))} ) : undefined, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [agentConfigData, agentData, getHref, isAgentConfigLoading] ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx index 0154f92576c4af..58f8528236bb96 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx @@ -73,6 +73,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = ); } + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const colorRange = useColorRange( COLOR_RANGE.BLUE, COLOR_RANGE_SCALE.INFLUENCER, diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx new file mode 100644 index 00000000000000..21a9fabf445f15 --- /dev/null +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; +import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; +import { Home } from '../pages/home'; +import { PluginContext } from '../context/plugin_context'; + +export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { + const i18nCore = core.i18n; + const isDarkMode = core.uiSettings.get('theme:darkMode'); + ReactDOM.render( + + + + + + + , + element + ); + return () => { + ReactDOM.unmountComponentAtNode(element); + }; +}; diff --git a/x-pack/plugins/observability/public/assets/observability_overview.png b/x-pack/plugins/observability/public/assets/observability_overview.png new file mode 100644 index 00000000000000..70be08af9745ad Binary files /dev/null and b/x-pack/plugins/observability/public/assets/observability_overview.png differ diff --git a/x-pack/plugins/observability/public/context/plugin_context.tsx b/x-pack/plugins/observability/public/context/plugin_context.tsx new file mode 100644 index 00000000000000..7d705e7a6cc055 --- /dev/null +++ b/x-pack/plugins/observability/public/context/plugin_context.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createContext } from 'react'; +import { AppMountContext } from 'kibana/public'; + +export interface PluginContextValue { + core: AppMountContext['core']; +} + +export const PluginContext = createContext({} as PluginContextValue); diff --git a/x-pack/plugins/observability/public/hooks/use_plugin_context.tsx b/x-pack/plugins/observability/public/hooks/use_plugin_context.tsx new file mode 100644 index 00000000000000..eeec115f0d2866 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_plugin_context.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useContext } from 'react'; +import { PluginContext } from '../context/plugin_context'; + +export function usePluginContext() { + return useContext(PluginContext); +} diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx new file mode 100644 index 00000000000000..072d3c47d3a555 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButton, + EuiCard, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiImage, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; +import styled from 'styled-components'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { appsSection, tryItOutItemsSection } from './section'; + +const Container = styled.div` + min-height: calc(100vh - 48px); + background: ${(props) => props.theme.eui.euiColorEmptyShade}; +`; + +const Title = styled.div` + background-color: ${(props) => props.theme.eui.euiPageBackgroundColor}; + border-bottom: ${(props) => props.theme.eui.euiBorderThin}; +`; + +const Page = styled.div` + width: 100%; + max-width: 1200px; + margin: 0 auto; + overflow: hidden; +} +`; + +const EuiCardWithoutPadding = styled(EuiCard)` + padding: 0; +`; + +export const Home = () => { + const { core } = usePluginContext(); + + useEffect(() => { + core.chrome.setBreadcrumbs([ + { + text: i18n.translate('xpack.observability.home.breadcrumb.observability', { + defaultMessage: 'Observability', + }), + }, + { + text: i18n.translate('xpack.observability.home.breadcrumb.gettingStarted', { + defaultMessage: 'Getting started', + }), + }, + ]); + }, [core]); + + return ( + + + <Page> + <EuiSpacer size="xxl" /> + <EuiFlexGroup> + <EuiFlexItem grow={false}> + <EuiIcon type="logoObservability" size="xxl" /> + </EuiFlexItem> + <EuiFlexItem> + <EuiTitle size="m"> + <h1> + {i18n.translate('xpack.observability.home.title', { + defaultMessage: 'Observability', + })} + </h1> + </EuiTitle> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="xxl" /> + </Page> + + + + + {/* title and description */} + + +

+ {i18n.translate('xpack.observability.home.sectionTitle', { + defaultMessage: 'Observability built on the Elastic Stack', + })} +

+
+ + + {i18n.translate('xpack.observability.home.sectionsubtitle', { + defaultMessage: + 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.', + })} + +
+ + {/* Apps sections */} + + + + + + {appsSection.map((app) => ( + + } + title={ + +

{app.title}

+
+ } + description={app.description} + /> +
+ ))} +
+
+ + + +
+
+ + {/* Get started button */} + + + + + {i18n.translate('xpack.observability.home.getStatedButton', { + defaultMessage: 'Get started', + })} + + + + + + + + {/* Try it out */} + + + + +

+ {i18n.translate('xpack.observability.home.tryItOut', { + defaultMessage: 'Try it out', + })} +

+
+
+
+
+ + {/* Try it out sections */} + + + {tryItOutItemsSection.map((item) => ( + + } + title={ + +

{item.title}

+
+ } + description={item.description} + target={item.target} + href={item.href} + /> +
+ ))} +
+ +
+
+
+
+ ); +}; diff --git a/x-pack/plugins/observability/public/pages/home/section.ts b/x-pack/plugins/observability/public/pages/home/section.ts new file mode 100644 index 00000000000000..f8bbfbfa305480 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/home/section.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; + +interface ISection { + id: string; + title: string; + icon: string; + description: string; + href?: string; + target?: '_blank'; +} + +export const appsSection: ISection[] = [ + { + id: 'logs', + title: i18n.translate('xpack.observability.section.apps.logs.title', { + defaultMessage: 'Logs', + }), + icon: 'logoLogging', + description: i18n.translate('xpack.observability.section.apps.logs.description', { + defaultMessage: + 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.', + }), + }, + { + id: 'apm', + title: i18n.translate('xpack.observability.section.apps.apm.title', { + defaultMessage: 'APM', + }), + icon: 'logoAPM', + description: i18n.translate('xpack.observability.section.apps.apm.description', { + defaultMessage: + 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.', + }), + }, + { + id: 'metrics', + title: i18n.translate('xpack.observability.section.apps.metrics.title', { + defaultMessage: 'Metrics', + }), + icon: 'logoMetrics', + description: i18n.translate('xpack.observability.section.apps.metrics.description', { + defaultMessage: + 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.', + }), + }, + { + id: 'uptime', + title: i18n.translate('xpack.observability.section.apps.uptime.title', { + defaultMessage: 'Uptime', + }), + icon: 'logoUptime', + description: i18n.translate('xpack.observability.section.apps.uptime.description', { + defaultMessage: + 'React to availability issues across your apps and services before they affect users.', + }), + }, +]; + +export const tryItOutItemsSection: ISection[] = [ + { + id: 'demo', + title: i18n.translate('xpack.observability.section.tryItOut.demo.title', { + defaultMessage: 'Demo Playground', + }), + icon: 'play', + description: '', + href: 'https://demo.elastic.co/', + target: '_blank', + }, + { + id: 'sampleData', + title: i18n.translate('xpack.observability.section.tryItOut.sampleData.title', { + defaultMessage: 'Add sample data', + }), + icon: 'documents', + description: '', + href: '/app/home#/tutorial_directory/sampleData', + }, +]; diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index a7eb1c50a03928..f2c88a7b1c0561 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -3,13 +3,37 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Plugin as PluginClass, PluginInitializerContext } from 'kibana/public'; +import { + AppMountParameters, + CoreSetup, + DEFAULT_APP_CATEGORIES, + Plugin as PluginClass, + PluginInitializerContext, +} from '../../../../src/core/public'; export type ClientSetup = void; export type ClientStart = void; -export class Plugin implements PluginClass { +export class Plugin implements PluginClass { constructor(context: PluginInitializerContext) {} - start() {} - setup() {} + + public setup(core: CoreSetup) { + core.application.register({ + id: 'observability-overview', + title: 'Overview', + order: 8000, + appRoute: '/app/observability', + category: DEFAULT_APP_CATEGORIES.observability, + + async mount(params: AppMountParameters) { + // Load application bundle + const { renderApp } = await import('./application'); + // Get start services + const [coreStart] = await core.getStartServices(); + + return renderApp(coreStart, params); + }, + }); + } + public start() {} } diff --git a/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts index 36cd4f280ac4cb..d0426a5e67e461 100644 --- a/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts +++ b/x-pack/plugins/painless_lab/public/application/hooks/use_submit_code.ts @@ -19,6 +19,7 @@ export const useSubmitCode = (http: HttpSetup) => { const [response, setResponse] = useState(undefined); const [inProgress, setInProgress] = useState(false); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const submit = useCallback( debounce( async (config: Payload) => { diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx index ad71059984a8b0..778c6bd92bc731 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_rule_actions/index.tsx @@ -162,7 +162,6 @@ const StepRuleActionsComponent: FC = ({ {myStepData.throttle !== stepActionsDefaultValue.throttle ? ( <> - = ({ messageVariables: actionMessageParams, }} /> - ) : ( = ({ component={GhostFormField} /> )} + diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 669fc89ffe8041..bb63cf633747b3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -70,7 +70,7 @@ export interface CaseProps extends Props { export const CaseComponent = React.memo( ({ caseId, caseData, fetchCase, updateCase, userCanCrud }) => { const basePath = window.location.origin + useBasePath(); - const caseLink = `${basePath}/app/siem#/case/${caseId}`; + const caseLink = `${basePath}/app/security#/case/${caseId}`; const search = useGetUrlSearch(navTabs.case); const [initLoadingData, setInitLoadingData] = useState(true); const { diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts index 9523e2485a6107..b8219ad52f5b0a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts @@ -143,7 +143,7 @@ export const CASE_REFRESH = i18n.translate('xpack.securitySolution.case.caseView export const EMAIL_SUBJECT = (caseTitle: string) => i18n.translate('xpack.securitySolution.case.caseView.emailSubject', { values: { caseTitle }, - defaultMessage: 'SIEM Case - {caseTitle}', + defaultMessage: 'Security Case - {caseTitle}', }); export const EMAIL_BODY = (caseUrl: string) => diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx index 43d5351a5dce3c..67963c7487826a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx @@ -52,7 +52,7 @@ describe('FieldMappingRow', () => { test('it pass the corrects props to mapping row', () => { const rows = wrapper.find(FieldMappingRow); rows.forEach((row, index) => { - expect(row.prop('siemField')).toEqual(mapping[index].source); + expect(row.prop('securitySolutionField')).toEqual(mapping[index].source); expect(row.prop('selectedActionType')).toEqual(mapping[index].actionType); expect(row.prop('selectedThirdParty')).toEqual(mapping[index].target); }); @@ -68,7 +68,7 @@ describe('FieldMappingRow', () => { const rows = newWrapper.find(FieldMappingRow); rows.forEach((row, index) => { - expect(row.prop('siemField')).toEqual(defaultMapping[index].source); + expect(row.prop('securitySolutionField')).toEqual(defaultMapping[index].source); expect(row.prop('selectedActionType')).toEqual(defaultMapping[index].actionType); expect(row.prop('selectedThirdParty')).toEqual(defaultMapping[index].target); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx index 3d515941fc2f33..415faa96eeeddc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx @@ -132,7 +132,7 @@ const FieldMappingComponent: React.FC = ({ key={`${item.source}`} id={`${item.source}`} disabled={disabled} - siemField={item.source} + securitySolutionField={item.source} thirdPartyOptions={getThirdPartyOptions(item.source, selectedConnector.fields)} actionTypeOptions={actionTypeOptions} onChangeActionType={onChangeActionType} diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx index 3787a43ff2d28a..a2acd0e20b6adc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.test.tsx @@ -51,7 +51,7 @@ describe('FieldMappingRow', () => { const props: RowProps = { id: 'title', disabled: false, - siemField: 'title', + securitySolutionField: 'title', thirdPartyOptions, actionTypeOptions, onChangeActionType, diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx index 922ea7222efce3..6e688b213f82c0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping_row.tsx @@ -20,7 +20,7 @@ import { AllThirdPartyFields } from '../../../common/lib/connectors/types'; export interface RowProps { id: string; disabled: boolean; - siemField: CaseField; + securitySolutionField: CaseField; thirdPartyOptions: Array>; actionTypeOptions: Array>; onChangeActionType: (caseField: CaseField, newActionType: ActionType) => void; @@ -32,7 +32,7 @@ export interface RowProps { const FieldMappingRowComponent: React.FC = ({ id, disabled, - siemField, + securitySolutionField, thirdPartyOptions, actionTypeOptions, onChangeActionType, @@ -40,13 +40,15 @@ const FieldMappingRowComponent: React.FC = ({ selectedActionType, selectedThirdParty, }) => { - const siemFieldCapitalized = useMemo(() => capitalize(siemField), [siemField]); + const securitySolutionFieldCapitalized = useMemo(() => capitalize(securitySolutionField), [ + securitySolutionField, + ]); return ( - {siemFieldCapitalized} + {securitySolutionFieldCapitalized} @@ -58,7 +60,7 @@ const FieldMappingRowComponent: React.FC = ({ disabled={disabled} options={thirdPartyOptions} valueOfSelected={selectedThirdParty} - onChange={onChangeThirdParty.bind(null, siemField)} + onChange={onChangeThirdParty.bind(null, securitySolutionField)} data-test-subj={`case-configure-third-party-select-${id}`} /> @@ -67,7 +69,7 @@ const FieldMappingRowComponent: React.FC = ({ disabled={disabled} options={actionTypeOptions} valueOfSelected={selectedActionType} - onChange={onChangeActionType.bind(null, siemField)} + onChange={onChangeActionType.bind(null, securitySolutionField)} data-test-subj={`case-configure-action-type-select-${id}`} /> diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts b/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts index c256c6dedb9189..9ef6ce2f3d4a93 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts @@ -17,7 +17,7 @@ export const INCIDENT_MANAGEMENT_SYSTEM_DESC = i18n.translate( 'xpack.securitySolution.case.configureCases.incidentManagementSystemDesc', { defaultMessage: - 'You may optionally connect SIEM cases to an external incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.', + 'You may optionally connect Security cases to an external incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.', } ); @@ -53,7 +53,7 @@ export const CASE_CLOSURE_OPTIONS_DESC = i18n.translate( 'xpack.securitySolution.case.configureCases.caseClosureOptionsDesc', { defaultMessage: - 'Define how you wish SIEM cases to be closed. Automated case closures require an established connection to an external incident management system.', + 'Define how you wish Security cases to be closed. Automated case closures require an established connection to an external incident management system.', } ); @@ -67,21 +67,22 @@ export const CASE_CLOSURE_OPTIONS_LABEL = i18n.translate( export const CASE_CLOSURE_OPTIONS_MANUAL = i18n.translate( 'xpack.securitySolution.case.configureCases.caseClosureOptionsManual', { - defaultMessage: 'Manually close SIEM cases', + defaultMessage: 'Manually close Security cases', } ); export const CASE_CLOSURE_OPTIONS_NEW_INCIDENT = i18n.translate( 'xpack.securitySolution.case.configureCases.caseClosureOptionsNewIncident', { - defaultMessage: 'Automatically close SIEM cases when pushing new incident to external system', + defaultMessage: + 'Automatically close Security cases when pushing new incident to external system', } ); export const CASE_CLOSURE_OPTIONS_CLOSED_INCIDENT = i18n.translate( 'xpack.securitySolution.case.configureCases.caseClosureOptionsClosedIncident', { - defaultMessage: 'Automatically close SIEM cases when incident is closed in external system', + defaultMessage: 'Automatically close Security cases when incident is closed in external system', } ); @@ -96,14 +97,14 @@ export const FIELD_MAPPING_DESC = i18n.translate( 'xpack.securitySolution.case.configureCases.fieldMappingDesc', { defaultMessage: - 'Map SIEM case fields when pushing data to a third-party. Field mappings require an established connection to an external incident management system.', + 'Map Security case fields when pushing data to a third-party. Field mappings require an established connection to an external incident management system.', } ); export const FIELD_MAPPING_FIRST_COL = i18n.translate( 'xpack.securitySolution.case.configureCases.fieldMappingFirstCol', { - defaultMessage: 'SIEM case field', + defaultMessage: 'Security case field', } ); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx index 8e07706d9c6ba1..ae9f1ec7469e41 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx @@ -18,7 +18,7 @@ const onSaveContent = jest.fn(); const timelineId = '1e10f150-949b-11ea-b63c-2bc51864784c'; const defaultProps = { - content: `A link to a timeline [timeline](http://localhost:5601/app/siem#/timelines?timeline=(id:'${timelineId}',isOpen:!t))`, + content: `A link to a timeline [timeline](http://localhost:5601/app/security#/timelines?timeline=(id:'${timelineId}',isOpen:!t))`, id: 'markdown-id', isEditable: false, onChangeEditable, diff --git a/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx b/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx index 936bd26adff7f8..687d2a36da610d 100644 --- a/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx @@ -14,6 +14,7 @@ import { LocalizedDateTooltip } from '../localized_date_tooltip'; import { getMaybeDate } from './maybe_date'; export const PreferenceFormattedDate = React.memo<{ dateFormat?: string; value: Date }>( + /* eslint-disable-next-line react-hooks/rules-of-hooks */ ({ value, dateFormat = useDateFormat() }) => ( <>{moment.tz(value, useTimeZone()).format(dateFormat)} ) diff --git a/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/overview/index.tsx b/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/overview/index.tsx index e2d222e3b836be..937e3727ca6135 100644 --- a/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/overview/index.tsx +++ b/x-pack/plugins/security_solution/public/endpoint_alerts/view/details/overview/index.tsx @@ -34,6 +34,7 @@ const AlertDetailsOverviewComponent = memo(() => { return null; } + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const tabs: EuiTabbedContentTab[] = useMemo(() => { return [ { @@ -71,11 +72,13 @@ const AlertDetailsOverviewComponent = memo(() => { ]; }, [alertDetailsData]); + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const activeTab = useMemo( () => (alertDetailsTabId ? tabs.find(({ id }) => id === alertDetailsTabId) : tabs[0]), [alertDetailsTabId, tabs] ); + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const handleTabClick = useCallback( (clickedTab: EuiTabbedContentTab): void => { if (clickedTab.id !== alertDetailsTabId) { diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx index 5cbfe2275d31c0..579c3311cf7322 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx @@ -22,6 +22,7 @@ export const FirstLastSeenHost = React.memo<{ hostname: string; type: FirstLastS return ( {(client) => { + /* eslint-disable-next-line react-hooks/rules-of-hooks */ const { loading, firstSeen, lastSeen, errorMessage } = useFirstLastSeenHostQuery( hostname, 'default', diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx index 8172165a77e78b..8db95f586782c7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx @@ -136,8 +136,14 @@ export const PolicyResponse = memo( const attentionCount = responseAttentionCount.get(key); return ( htmlIdGenerator()(), [])} - key={useMemo(() => htmlIdGenerator()(), [])} + id={ + /* eslint-disable-next-line react-hooks/rules-of-hooks */ + useMemo(() => htmlIdGenerator()(), []) + } + key={ + /* eslint-disable-next-line react-hooks/rules-of-hooks */ + useMemo(() => htmlIdGenerator()(), []) + } data-test-subj="hostDetailsPolicyResponseConfigAccordion" buttonContent={ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/fetch_kql_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/fetch_kql_timeline.tsx index e75f87e0d6011a..77bd9aeba3ed24 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/fetch_kql_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/fetch_kql_timeline.tsx @@ -30,6 +30,7 @@ const TimelineKqlFetchComponent = memo( inputId, inspect: null, loading: false, + /* eslint-disable-next-line react-hooks/rules-of-hooks */ refetch: useUpdateKql({ indexPattern, kueryFilterQuery, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx index ede8d7cfded582..1038ac4b695872 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx @@ -18,7 +18,7 @@ const endDate = new Date('2018-03-24T03:33:52.253Z').valueOf(); describe('Build KQL Query', () => { test('Build KQL query with one data provider', () => { - const dataProviders = mockDataProviders.slice(0, 1); + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 1"'); }); @@ -56,18 +56,40 @@ describe('Build KQL Query', () => { }); test('Build KQL query with two data provider', () => { - const dataProviders = mockDataProviders.slice(0, 2); + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual('(name : "Provider 1") or (name : "Provider 2")'); + }); + + test('Build KQL query with two data provider and first is disabled', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + dataProviders[0].enabled = false; + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 2"'); + }); + + test('Build KQL query with two data provider and second is disabled', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + dataProviders[1].enabled = false; const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); - expect(cleanUpKqlQuery(kqlQuery)).toEqual('(name : "Provider 1" ) or (name : "Provider 2" )'); + expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 1"'); }); test('Build KQL query with one data provider and one and', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - dataProviders[0].and = mockDataProviders.slice(1, 2); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(1, 2)); const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 1" and name : "Provider 2"'); }); + test('Build KQL query with one disabled data provider and one and', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].enabled = false; + dataProviders[0].and = cloneDeep(mockDataProviders.slice(1, 2)); + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 2"'); + }); + test('Build KQL query with one data provider and one and as timestamp (string input)', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); dataProviders[0].and = cloneDeep(mockDataProviders.slice(1, 2)); @@ -106,28 +128,50 @@ describe('Build KQL Query', () => { test('Build KQL query with two data provider and multiple and', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); - dataProviders[0].and = mockDataProviders.slice(2, 4); - dataProviders[1].and = mockDataProviders.slice(4, 5); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual( '(name : "Provider 1" and name : "Provider 3" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5")' ); }); + test('Build KQL query with two data provider and multiple and and first data provider is disabled', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + dataProviders[0].enabled = false; + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual( + '(name : "Provider 3" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5")' + ); + }); + + test('Build KQL query with two data provider and multiple and and first and provider is disabled', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[0].and[0].enabled = false; + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual( + '(name : "Provider 1" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5")' + ); + }); + test('Build KQL query with all data provider', () => { const kqlQuery = buildGlobalQuery(mockDataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual( - '(name : "Provider 1" ) or (name : "Provider 2" ) or (name : "Provider 3" ) or (name : "Provider 4" ) or (name : "Provider 5" ) or (name : "Provider 6" ) or (name : "Provider 7" ) or (name : "Provider 8" ) or (name : "Provider 9" ) or (name : "Provider 10" )' + '(name : "Provider 1") or (name : "Provider 2") or (name : "Provider 3") or (name : "Provider 4") or (name : "Provider 5") or (name : "Provider 6") or (name : "Provider 7") or (name : "Provider 8") or (name : "Provider 9") or (name : "Provider 10")' ); }); test('Build complex KQL query with and and or', () => { const dataProviders = cloneDeep(mockDataProviders); - dataProviders[0].and = mockDataProviders.slice(2, 4); - dataProviders[1].and = mockDataProviders.slice(4, 5); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual( - '(name : "Provider 1" and name : "Provider 3" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5") or (name : "Provider 3" ) or (name : "Provider 4" ) or (name : "Provider 5" ) or (name : "Provider 6" ) or (name : "Provider 7" ) or (name : "Provider 8" ) or (name : "Provider 9" ) or (name : "Provider 10" )' + '(name : "Provider 1" and name : "Provider 3" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5") or (name : "Provider 3") or (name : "Provider 4") or (name : "Provider 5") or (name : "Provider 6") or (name : "Provider 7") or (name : "Provider 8") or (name : "Provider 9") or (name : "Provider 10")' ); }); }); @@ -223,7 +267,7 @@ describe('Combined Queries', () => { }); test('Only Data Provider', () => { - const dataProviders = mockDataProviders.slice(0, 1); + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); const { filterQuery } = combineQueries({ config, dataProviders, @@ -338,7 +382,7 @@ describe('Combined Queries', () => { }); test('Data Provider & KQL search query', () => { - const dataProviders = mockDataProviders.slice(0, 1); + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); const { filterQuery } = combineQueries({ config, dataProviders, @@ -356,7 +400,7 @@ describe('Combined Queries', () => { }); test('Data Provider & KQL filter query', () => { - const dataProviders = mockDataProviders.slice(0, 1); + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); const { filterQuery } = combineQueries({ config, dataProviders, @@ -375,8 +419,8 @@ describe('Combined Queries', () => { test('Data Provider & KQL search query multiple', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); - dataProviders[0].and = mockDataProviders.slice(2, 4); - dataProviders[1].and = mockDataProviders.slice(4, 5); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); const { filterQuery } = combineQueries({ config, dataProviders, @@ -395,8 +439,8 @@ describe('Combined Queries', () => { test('Data Provider & KQL filter query multiple', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); - dataProviders[0].and = mockDataProviders.slice(2, 4); - dataProviders[1].and = mockDataProviders.slice(4, 5); + dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); + dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); const { filterQuery } = combineQueries({ config, dataProviders, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx index da74d22575f855..b5481e9d4eee24 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx @@ -63,35 +63,30 @@ const buildQueryMatch = ( : `${dataProvider.queryMatch.field} ${EXISTS_OPERATOR}` }`.trim(); -const buildQueryForAndProvider = ( - dataAndProviders: DataProvidersAnd[], - browserFields: BrowserFields -) => - dataAndProviders - .reduce((andQuery, andDataProvider) => { - const prepend = (q: string) => `${q !== '' ? `${q} and ` : ''}`; - return andDataProvider.enabled - ? `${prepend(andQuery)} ${buildQueryMatch(andDataProvider, browserFields)}` - : andQuery; - }, '') - .trim(); - export const buildGlobalQuery = (dataProviders: DataProvider[], browserFields: BrowserFields) => dataProviders - .reduce((query, dataProvider: DataProvider, i) => { - const prepend = (q: string) => `${q !== '' ? `${q} or ` : ''}`; - const openParen = i >= 0 && dataProviders.length > 1 ? '(' : ''; - const closeParen = i >= 0 && dataProviders.length > 1 ? ')' : ''; - return dataProvider.enabled - ? `${prepend(query)}${openParen}${buildQueryMatch(dataProvider, browserFields)} - ${ - dataProvider.and.length > 0 - ? ` and ${buildQueryForAndProvider(dataProvider.and, browserFields)}` - : '' - }${closeParen}`.trim() - : query; - }, '') - .trim(); + .reduce((queries: string[], dataProvider: DataProvider) => { + const flatDataProviders = [dataProvider, ...dataProvider.and]; + const activeDataProviders = flatDataProviders.filter( + (flatDataProvider) => flatDataProvider.enabled + ); + + if (!activeDataProviders.length) return queries; + + const activeDataProvidersQueries = activeDataProviders.map((activeDataProvider) => + buildQueryMatch(activeDataProvider, browserFields) + ); + + const activeDataProvidersQueryMatch = activeDataProvidersQueries.join(' and '); + + return [...queries, activeDataProvidersQueryMatch]; + }, []) + .filter((queriesItem) => !isEmpty(queriesItem)) + .reduce((globalQuery: string, queryMatch: string, index: number, queries: string[]) => { + if (queries.length <= 1) return queryMatch; + + return !index ? `(${queryMatch})` : `${globalQuery} or (${queryMatch})`; + }, ''); export const combineQueries = ({ config, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules_notifications.ts index 8fceb8ef720b5b..0dfe68f132b06d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules_notifications.ts @@ -45,8 +45,5 @@ export const updateRulesNotifications = async ({ interval: ruleActions.alertThrottle, }); - // TODO: Workaround for https://github.com/elastic/kibana/issues/67290 - await alertsClient.updateApiKey({ id: ruleAlertId }); - return ruleActions; }; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx index b48deb771c873f..67be9fb7ca7936 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx @@ -30,6 +30,7 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm const [options, setOptions] = useState([]); const [isLoading, setIsLoading] = useState(true); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ const fetchOptions = useCallback( debounce(async (searchValue: string) => { const esSearchRequest = { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 7ce952e9b3e0ad..7db6b5145f8950 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment } from 'react'; +import React, { Fragment, lazy } from 'react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { ReactWrapper } from 'enzyme'; @@ -18,6 +18,13 @@ jest.mock('../../lib/action_connector_api', () => ({ const actionTypeRegistry = actionTypeRegistryMock.create(); describe('action_form', () => { let deps: any; + + const mockedActionParamsFields = lazy(async () => ({ + default() { + return ; + }, + })); + const alertType = { id: 'my-alert-type', iconClass: 'test', @@ -41,7 +48,7 @@ describe('action_form', () => { return validationResult; }, actionConnectorFields: null, - actionParamsFields: null, + actionParamsFields: mockedActionParamsFields, }; const disabledByConfigActionType = { @@ -56,7 +63,7 @@ describe('action_form', () => { return validationResult; }, actionConnectorFields: null, - actionParamsFields: null, + actionParamsFields: mockedActionParamsFields, }; const disabledByLicenseActionType = { @@ -71,7 +78,7 @@ describe('action_form', () => { return validationResult; }, actionConnectorFields: null, - actionParamsFields: null, + actionParamsFields: mockedActionParamsFields, }; const preconfiguredOnly = { @@ -86,6 +93,21 @@ describe('action_form', () => { return validationResult; }, actionConnectorFields: null, + actionParamsFields: mockedActionParamsFields, + }; + + const actionTypeWithoutParams = { + id: 'my-action-type-without-params', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, actionParamsFields: null, }; @@ -153,6 +175,7 @@ describe('action_form', () => { disabledByConfigActionType, disabledByLicenseActionType, preconfiguredOnly, + actionTypeWithoutParams, ]); actionTypeRegistry.has.mockReturnValue(true); actionTypeRegistry.get.mockReturnValue(actionType); @@ -237,6 +260,14 @@ describe('action_form', () => { enabledInLicense: false, minimumLicenseRequired: 'gold', }, + { + id: actionTypeWithoutParams.id, + name: 'Action type without params', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'basic', + }, ]} toastNotifications={deps!.toastNotifications} docLinks={deps.docLinks} @@ -340,5 +371,13 @@ describe('action_form', () => { .exists() ).toBeTruthy(); }); + + it(`shouldn't render action types without params component`, async () => { + await setup(); + const actionOption = wrapper.find( + `[data-test-subj="${actionTypeWithoutParams.id}-ActionTypeSelectOption"]` + ); + expect(actionOption.exists()).toBeFalsy(); + }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 4c3a8d133922d3..201852ddeee488 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -547,6 +547,7 @@ export const ActionForm = ({ actionTypeNodes = actionTypeRegistry .list() .filter((item) => actionTypesIndex[item.id]) + .filter((item) => !!item.actionParamsFields) .sort((a, b) => actionTypeCompare(actionTypesIndex[a.id], actionTypesIndex[b.id], preconfiguredConnectors) ) diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index 5d9bbacb490067..d3a67f81004da5 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -56,7 +56,7 @@ export class UptimePlugin appRoute: '/app/uptime#/', id: PLUGIN.ID, euiIconType: 'uptimeApp', - order: 8900, + order: 8400, title: PLUGIN.TITLE, category: DEFAULT_APP_CATEGORIES.observability, mount: async (params: AppMountParameters) => { diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts index 96f32a0d54db4c..2f8291991f3dd6 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts @@ -69,7 +69,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`allows settings to be changed`, async () => { @@ -125,7 +125,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`does not allow settings to be changed`, async () => { @@ -177,7 +177,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Discover', 'Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`does not allow navigation to advanced settings; redirects to management home`, async () => { diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts index aa125430046562..4c3c1556d621c3 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts @@ -60,7 +60,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows apm navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['APM', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.contain('APM'); }); it('can navigate to APM app', async () => { @@ -109,7 +109,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows apm navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['APM', 'Stack Management']); + expect(navLinks).to.contain('APM'); }); it('can navigate to APM app', async () => { diff --git a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts index e9fa4ccf8e48ba..b776d358b16733 100644 --- a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts +++ b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts @@ -66,7 +66,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows canvas navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Canvas', 'Stack Management']); + expect(navLinks).to.contain('Canvas'); }); it(`landing page shows "Create new workpad" button`, async () => { @@ -142,7 +142,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows canvas navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Canvas', 'Stack Management']); + expect(navLinks).to.contain('Canvas'); }); it(`landing page shows disabled "Create new workpad" button`, async () => { diff --git a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts index 803ff6399a035e..5d5f6b8aaa324e 100644 --- a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts +++ b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts @@ -63,7 +63,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Dev Tools navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Dev Tools', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.contain('Dev Tools'); }); describe('console', () => { @@ -144,7 +144,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`shows 'Dev Tools' navlink`, async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Dev Tools', 'Stack Management']); + expect(navLinks).to.contain('Dev Tools'); }); describe('console', () => { diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts index 03a5cc6ac8fa03..6a11daa8d2c260 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts @@ -82,7 +82,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Discover', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.contain('Discover'); }); it('shows save button', async () => { @@ -169,7 +169,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Discover', 'Stack Management']); + expect(navLinks).to.contain('Discover'); }); it(`doesn't show save button`, async () => { @@ -260,7 +260,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Discover', 'Stack Management']); + expect(navLinks).to.contain('Discover'); }); it(`doesn't show save button`, async () => { diff --git a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts index 9121028c14404c..f13d73bc95dbee 100644 --- a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts +++ b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts @@ -64,7 +64,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows graph navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Graph', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.contain('Graph'); }); it('landing page shows "Create new graph" button', async () => { @@ -127,7 +127,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows graph navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Graph', 'Stack Management']); + expect(navLinks).to.contain('Graph'); }); it('does not show a "Create new Workspace" button', async () => { diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts index cd892c44242907..a6d2c13cd2b314 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts @@ -71,7 +71,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`index pattern listing shows create button`, async () => { @@ -125,7 +125,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`index pattern listing doesn't show create button`, async () => { @@ -177,7 +177,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Management navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Discover', 'Stack Management']); + expect(navLinks).to.contain('Stack Management'); }); it(`doesn't show Index Patterns in management side-nav`, async () => { diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts index 23ced6a6c5c68f..a04b2bfeaba51a 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -61,7 +61,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows metrics navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Metrics', 'Stack Management']); + expect(navLinks).to.contain('Metrics'); }); describe('infrastructure landing page without data', () => { @@ -177,7 +177,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows metrics navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Metrics', 'Stack Management']); + expect(navLinks).to.contain('Metrics'); }); describe('infrastructure landing page without data', () => { diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts index 9a95e50881a429..5f025db62ff6d8 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts @@ -58,7 +58,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows logs navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Logs', 'Stack Management']); + expect(navLinks).to.contain('Logs'); }); describe('logs landing page without data', () => { @@ -121,7 +121,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows logs navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Logs', 'Stack Management']); + expect(navLinks).to.contain('Logs'); }); describe('logs landing page without data', () => { diff --git a/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts b/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts index 2449430ac85c28..f1c5b3f82f7da9 100644 --- a/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts +++ b/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts @@ -66,7 +66,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows maps navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Maps', 'Stack Management']); + expect(navLinks).to.contain('Maps'); }); it(`allows a map to be created`, async () => { @@ -153,7 +153,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows Maps navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Maps', 'Stack Management']); + expect(navLinks).to.contain('Maps'); }); it(`does not show create new button`, async () => { diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts index d7565fd846e707..c8baa13b564086 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts @@ -11,7 +11,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('classification creation', function () { + // flaky test, see https://github.com/elastic/kibana/issues/68356 + describe.skip('classification creation', function () { before(async () => { await esArchiver.loadIfNeeded('ml/bm_classification'); await ml.testResources.createIndexPatternIfNeeded('ft_bank_marketing', '@timestamp'); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts index 7c59664fdec354..c818619a183786 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts @@ -11,7 +11,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('regression creation', function () { + // flaky test, see https://github.com/elastic/kibana/issues/68352 + describe.skip('regression creation', function () { before(async () => { await esArchiver.loadIfNeeded('ml/egs_regression'); await ml.testResources.createIndexPatternIfNeeded('ft_egs_regression', '@timestamp'); diff --git a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts index a3ade23f5c178b..5021bd8cce0fcd 100644 --- a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts +++ b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts @@ -60,7 +60,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows timelion navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Timelion', 'Stack Management']); + expect(navLinks).to.contain('Timelion'); }); it(`allows a timelion sheet to be created`, async () => { @@ -112,7 +112,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows timelion navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Timelion', 'Stack Management']); + expect(navLinks).to.contain('Timelion'); }); it(`does not allow a timelion sheet to be created`, async () => { diff --git a/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts b/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts index ae13cf07424320..991cd07dce5138 100644 --- a/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts +++ b/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts @@ -64,7 +64,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows uptime navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Uptime', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.contain('Uptime'); }); it('can navigate to Uptime app', async () => { @@ -115,7 +115,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows uptime navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Uptime', 'Stack Management']); + expect(navLinks).to.contain('Uptime'); }); it('can navigate to Uptime app', async () => { diff --git a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts index 9410a6f9435f2f..f74643939477c1 100644 --- a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts +++ b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts @@ -77,7 +77,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Visualize', 'Stack Management']); + expect(navLinks).to.contain('Visualize'); }); it(`landing page shows "Create new Visualization" button`, async () => { @@ -201,7 +201,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Visualize', 'Stack Management']); + expect(navLinks).to.contain('Visualize'); }); it(`landing page shows "Create new Visualization" button`, async () => { @@ -316,7 +316,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Visualize', 'Stack Management']); + expect(navLinks).to.contain('Visualize'); }); it(`landing page shows "Create new Visualization" button`, async () => { diff --git a/x-pack/test/page_load_metrics/config.ts b/x-pack/test/page_load_metrics/config.ts new file mode 100644 index 00000000000000..641099ff8e9346 --- /dev/null +++ b/x-pack/test/page_load_metrics/config.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resolve } from 'path'; + +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { PuppeteerTestRunner } from './runner'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaCommonTestsConfig = await readConfigFile( + require.resolve('../../../test/common/config.js') + ); + const xpackFunctionalTestsConfig = await readConfigFile( + require.resolve('../functional/config.js') + ); + + return { + ...kibanaCommonTestsConfig.getAll(), + + testRunner: PuppeteerTestRunner, + + esArchiver: { + directory: resolve(__dirname, 'es_archives'), + }, + + screenshots: { + directory: resolve(__dirname, 'screenshots'), + }, + + esTestCluster: { + ...xpackFunctionalTestsConfig.get('esTestCluster'), + serverArgs: [...xpackFunctionalTestsConfig.get('esTestCluster.serverArgs')], + }, + + kbnTestServer: { + ...xpackFunctionalTestsConfig.get('kbnTestServer'), + }, + }; +} diff --git a/x-pack/test/page_load_metrics/es_archives/default/data.json.gz b/x-pack/test/page_load_metrics/es_archives/default/data.json.gz new file mode 100644 index 00000000000000..5a5290ddf64478 Binary files /dev/null and b/x-pack/test/page_load_metrics/es_archives/default/data.json.gz differ diff --git a/x-pack/test/page_load_metrics/es_archives/default/mappings.json b/x-pack/test/page_load_metrics/es_archives/default/mappings.json new file mode 100644 index 00000000000000..c36f9576c4df12 --- /dev/null +++ b/x-pack/test/page_load_metrics/es_archives/default/mappings.json @@ -0,0 +1,2402 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "ae24d22d5986d04124cc6568f771066f", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "21c3ea0763beb1ecb0162529706b88c5", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "bfd39d88aadadb4be597ea984d433dbe", + "metrics-explorer-view": "428e319af3e822c80a84cf87123ca35c", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "296a89039fc4260292be36b1b005d8f2", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "b675c3be8d76ecf029294d51dc7ec65d", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "properties": { + "agents": { + "properties": { + "dotnet": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "go": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "java": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "js-base": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "nodejs": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "python": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "ruby": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "rum-js": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + } + } + }, + "cardinality": { + "properties": { + "transaction": { + "properties": { + "name": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + }, + "user_agent": { + "properties": { + "original": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "counts": { + "properties": { + "agent_configuration": { + "properties": { + "all": { + "type": "long" + } + } + }, + "error": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "max_error_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "max_transaction_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "services": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "sourcemap": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "span": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "traces": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + } + } + }, + "has_any_services": { + "type": "boolean" + }, + "indices": { + "properties": { + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + }, + "shards": { + "properties": { + "total": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "ml": { + "properties": { + "all_jobs_count": { + "type": "long" + } + } + } + } + }, + "retainment": { + "properties": { + "error": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "span": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + }, + "tasks": { + "properties": { + "agent_configuration": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "agents": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "cardinality": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "groupings": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "indices_stats": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "processor_events": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "versions": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "version": { + "properties": { + "apm_server": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + }, + "patch": { + "type": "long" + } + } + } + } + } + } + }, + "application_usage_totals": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + } + } + }, + "application_usage_transactional": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + }, + "timestamp": { + "type": "date" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "defaultIndex": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "properties": { + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "emsVectorLayersCount": { + "dynamic": "true", + "type": "object" + }, + "layerTypesCount": { + "dynamic": "true", + "type": "object" + }, + "layersCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + } + } + }, + "indexPatternsWithGeoFieldCount": { + "type": "long" + }, + "indexPatternsWithGeoPointFieldCount": { + "type": "long" + }, + "indexPatternsWithGeoShapeFieldCount": { + "type": "long" + }, + "mapsTotalCount": { + "type": "long" + }, + "settings": { + "properties": { + "showMapVisualizationTypes": { + "type": "boolean" + } + } + }, + "timeCaptured": { + "type": "date" + } + } + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "type": "keyword" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "integer" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "type": "keyword" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "type": "keyword" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "test", + "mappings": { + "properties": { + "foo": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/page_load_metrics/runner.ts b/x-pack/test/page_load_metrics/runner.ts new file mode 100644 index 00000000000000..05f293730f843a --- /dev/null +++ b/x-pack/test/page_load_metrics/runner.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CiStatsReporter } from '@kbn/dev-utils'; +import { capturePageLoadMetrics } from '@kbn/test'; +// @ts-ignore not TS yet +import getUrl from '../../../src/test_utils/get_url'; + +import { FtrProviderContext } from './../functional/ftr_provider_context'; + +export async function PuppeteerTestRunner({ getService }: FtrProviderContext) { + const log = getService('log'); + const config = getService('config'); + const esArchiver = getService('esArchiver'); + + await esArchiver.load('default'); + const metrics = await capturePageLoadMetrics(log, { + headless: true, + appConfig: { + url: getUrl.baseUrl(config.get('servers.kibana')), + username: config.get('servers.kibana.username'), + password: config.get('servers.kibana.password'), + }, + screenshotsDir: config.get('screenshots.directory'), + }); + const reporter = CiStatsReporter.fromEnv(log); + + log.debug('Report page load asset size'); + await reporter.metrics(metrics); +} diff --git a/yarn.lock b/yarn.lock index 6ad6aab02949e8..11cc907dce4583 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4581,6 +4581,13 @@ dependencies: "@types/node" "*" +"@types/puppeteer@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-3.0.0.tgz#24cdcc131e319477608d893f0017e08befd70423" + integrity sha512-59+fkfHHXHzX5rgoXIMnZyzum7ZLx/Wc3fhsOduFThpTpKbzzdBHMZsrkKGLunimB4Ds/tI5lXTRLALK8Mmnhg== + dependencies: + "@types/node" "*" + "@types/q@^1.5.1": version "1.5.2" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" @@ -7639,6 +7646,15 @@ bl@^3.0.0: dependencies: readable-stream "^3.0.1" +bl@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" + integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + blob@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" @@ -8152,6 +8168,14 @@ buffer@^5.1.0, buffer@^5.2.0: base64-js "^1.0.2" ieee754 "^1.1.4" +buffer@^5.2.1, buffer@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -12657,10 +12681,10 @@ eslint-plugin-prettier@^3.1.3: dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-react-hooks@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.3.0.tgz#53e073961f1f5ccf8dd19558036c1fac8c29d99a" - integrity sha512-gLKCa52G4ee7uXzdLiorca7JIQZPPXRAQDXV83J4bUEeUuc5pIEyZYAZ45Xnxe5IuupxEqHS+hUhSLIimK1EMw== +eslint-plugin-react-hooks@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.4.tgz#aed33b4254a41b045818cacb047b81e6df27fa58" + integrity sha512-equAdEIsUETLFNCmmCkiCGq6rkSK5MoJhXFPFYeUebcjKgBmWWcgVOqZyQC8Bv1BwVCnTq9tBxgJFgAJTWoJtA== eslint-plugin-react-perf@^3.2.3: version "3.2.3" @@ -16615,7 +16639,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@~2.0.3, inherits@~2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -20732,6 +20756,11 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -23849,6 +23878,22 @@ puppeteer@^2.0.0: rimraf "^2.6.1" ws "^6.1.0" +puppeteer@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-3.3.0.tgz#95839af9fdc0aa4de7e5ee073a4c0adeb9e2d3d7" + integrity sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw== + dependencies: + debug "^4.1.0" + extract-zip "^2.0.0" + https-proxy-agent "^4.0.0" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^3.0.2" + tar-fs "^2.0.0" + unbzip2-stream "^1.3.3" + ws "^7.2.3" + q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -26184,6 +26229,13 @@ rimraf@^2.5.4, rimraf@^2.7.1: dependencies: glob "^7.1.3" +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + rimraf@~2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.0.3.tgz#f50a2965e7144e9afd998982f15df706730f56a9" @@ -28509,6 +28561,16 @@ tar-fs@^1.16.3: pump "^1.0.0" tar-stream "^1.1.2" +tar-fs@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5" + integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" + tar-stream@^1.1.2, tar-stream@^1.5.2: version "1.5.5" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55" @@ -28519,6 +28581,17 @@ tar-stream@^1.1.2, tar-stream@^1.5.2: readable-stream "^2.0.0" xtend "^4.0.0" +tar-stream@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" + integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q== + dependencies: + bl "^4.0.1" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar-stream@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" @@ -28808,7 +28881,7 @@ through2@~2.0.3: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3.4, through@~2.3.6, through@~2.3.8: +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3.4, through@~2.3.6, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -29997,6 +30070,14 @@ unbzip2-stream@^1.0.9: buffer "^3.0.1" through "^2.3.6" +unbzip2-stream@^1.3.3: + version "1.4.2" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz#84eb9e783b186d8fb397515fbb656f312f1a7dbf" + integrity sha512-pZMVAofMrrHX6Ik39hCk470kulCbmZ2SWfQLPmTWqfJV/oUm0gn1CblvHdUu4+54Je6Jq34x8kY6XjTy6dMkOg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -31862,6 +31943,11 @@ ws@^7.0.0: dependencies: async-limiter "^1.0.0" +ws@^7.2.3: + version "7.3.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" + integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w== + ws@~3.3.1: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"