diff --git a/.ci/Jenkinsfile_coverage b/.ci/Jenkinsfile_coverage index 63798e2e29e44..3f4732f15f334 100644 --- a/.ci/Jenkinsfile_coverage +++ b/.ci/Jenkinsfile_coverage @@ -23,6 +23,8 @@ def handleIngestion(timestamp) { kibanaPipeline.downloadCoverageArtifacts() kibanaCoverage.prokLinks("### Process HTML Links") kibanaCoverage.collectVcsInfo("### Collect VCS Info") + kibanaCoverage.generateReports("### Merge coverage reports") + kibanaCoverage.uploadCombinedReports() kibanaCoverage.ingest(timestamp, '### Injest && Upload') kibanaCoverage.uploadCoverageStaticSite(timestamp) } diff --git a/.ci/packer_cache.sh b/.ci/packer_cache.sh index ab68a60dcfc27..d47ef93172a9d 100755 --- a/.ci/packer_cache.sh +++ b/.ci/packer_cache.sh @@ -35,20 +35,20 @@ mkdir -p ".geckodriver" cp "node_modules/geckodriver/geckodriver.tar.gz" .geckodriver/geckodriver.tar.gz echo "$geckodriverPkgVersion" > .geckodriver/pkgVersion +echo "Creating bootstrap_cache archive" + # archive cacheable directories mkdir -p "$HOME/.kibana/bootstrap_cache" tar -cf "$HOME/.kibana/bootstrap_cache/$branch.tar" \ - node_modules \ - packages/*/node_modules \ - x-pack/node_modules \ - x-pack/legacy/plugins/*/node_modules \ x-pack/legacy/plugins/reporting/.chromium \ - test/plugin_functional/plugins/*/node_modules \ - examples/*/node_modules \ .es \ .chromedriver \ .geckodriver; +echo "Adding node_modules" +# Find all of the node_modules directories that aren't test fixtures, and aren't inside other node_modules directories, and append them to the tar +find . -type d -name node_modules -not -path '*__fixtures__*' -prune -print0 | xargs -0I % tar -rf "$HOME/.kibana/bootstrap_cache/$branch.tar" "%" + echo "created $HOME/.kibana/bootstrap_cache/$branch.tar" if [ "$branch" == "master" ]; then diff --git a/.eslintrc.js b/.eslintrc.js index aeaf6e04fdc01..ec5916c9a3ac7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -202,6 +202,11 @@ module.exports = { from: ['(src|x-pack)/plugins/*/server/**/*'], errorMessage: `Public code can not import from server, use a common directory.`, }, + { + target: ['(src|x-pack)/plugins/*/common/**/*'], + from: ['(src|x-pack)/plugins/*/(server|public)/**/*'], + errorMessage: `Common code can not import from server or public, use a common directory.`, + }, { target: [ '(src|x-pack)/legacy/**/*', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c3da7c7f00e96..83f4f90b9204d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -176,7 +176,7 @@ /x-pack/plugins/telemetry_collection_xpack/ @elastic/pulse # Kibana Alerting Services -/x-pack/plugins/alerting/ @elastic/kibana-alerting-services +/x-pack/plugins/alerts/ @elastic/kibana-alerting-services /x-pack/plugins/actions/ @elastic/kibana-alerting-services /x-pack/plugins/event_log/ @elastic/kibana-alerting-services /x-pack/plugins/task_manager/ @elastic/kibana-alerting-services diff --git a/docs/canvas/canvas-elements.asciidoc b/docs/canvas/canvas-elements.asciidoc index 4149039a3f87b..9c7467bb452fd 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 9cd4ecc9519e1..e0139ab943104 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 5cae3fcc7b531..a095253c6cff3 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 a38ab4a69598e..9b23817de2767 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 42eedf55c404d..ac2d348920114 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 0000000000000..1cda5b572acef 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 0000000000000..7a71686f04491 Binary files /dev/null and b/docs/canvas/images/canvas-refresh-data.png differ diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md new file mode 100644 index 0000000000000..d428faa500faf --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) > [applications$](./kibana-plugin-core-public.applicationstart.applications_.md) + +## ApplicationStart.applications$ property + +Observable emitting the list of currently registered apps and their associated status. + +Signature: + +```typescript +applications$: Observable>; +``` + +## Remarks + +Applications disabled by [Capabilities](./kibana-plugin-core-public.capabilities.md) will not be present in the map. Applications manually disabled from the client-side using an [application updater](./kibana-plugin-core-public.appupdater.md) are present, with their status properly set as `inaccessible`. + diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md index 6f45bab3ebd2d..896de2de32dd5 100644 --- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md @@ -15,6 +15,7 @@ export interface ApplicationStart | Property | Type | Description | | --- | --- | --- | +| [applications$](./kibana-plugin-core-public.applicationstart.applications_.md) | Observable<ReadonlyMap<string, PublicAppInfo | PublicLegacyAppInfo>> | Observable emitting the list of currently registered apps and their associated status. | | [capabilities](./kibana-plugin-core-public.applicationstart.capabilities.md) | RecursiveReadonly<Capabilities> | Gets the read-only capabilities. | | [currentAppId$](./kibana-plugin-core-public.applicationstart.currentappid_.md) | Observable<string | undefined> | An observable that emits the current application id and each subsequent id update. | diff --git a/docs/development/core/public/kibana-plugin-core-public.appmountparameters.onappleave.md b/docs/development/core/public/kibana-plugin-core-public.appmountparameters.onappleave.md index 7f72d6a52fc2a..e898126a553e2 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appmountparameters.onappleave.md +++ b/docs/development/core/public/kibana-plugin-core-public.appmountparameters.onappleave.md @@ -23,10 +23,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Route } from 'react-router-dom'; -import { CoreStart, AppMountParams } from 'src/core/public'; +import { CoreStart, AppMountParameters } from 'src/core/public'; import { MyPluginDepsStart } from './plugin'; -export renderApp = ({ element, history, onAppLeave }: AppMountParams) => { +export renderApp = ({ element, history, onAppLeave }: AppMountParameters) => { const { renderApp, hasUnsavedChanges } = await import('./application'); onAppLeave(actions => { if(hasUnsavedChanges()) { diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md new file mode 100644 index 0000000000000..292bf29962839 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [appUrl](./kibana-plugin-core-public.legacyapp.appurl.md) + +## LegacyApp.appUrl property + +Signature: + +```typescript +appUrl: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md new file mode 100644 index 0000000000000..af4d0eb7969d3 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [disableSubUrlTracking](./kibana-plugin-core-public.legacyapp.disablesuburltracking.md) + +## LegacyApp.disableSubUrlTracking property + +Signature: + +```typescript +disableSubUrlTracking?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md new file mode 100644 index 0000000000000..fa1314b74fd83 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [linkToLastSubUrl](./kibana-plugin-core-public.legacyapp.linktolastsuburl.md) + +## LegacyApp.linkToLastSubUrl property + +Signature: + +```typescript +linkToLastSubUrl?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.md new file mode 100644 index 0000000000000..06533aaa99170 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) + +## LegacyApp interface + + +Signature: + +```typescript +export interface LegacyApp extends AppBase +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [appUrl](./kibana-plugin-core-public.legacyapp.appurl.md) | string | | +| [disableSubUrlTracking](./kibana-plugin-core-public.legacyapp.disablesuburltracking.md) | boolean | | +| [linkToLastSubUrl](./kibana-plugin-core-public.legacyapp.linktolastsuburl.md) | boolean | | +| [subUrlBase](./kibana-plugin-core-public.legacyapp.suburlbase.md) | string | | + diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md new file mode 100644 index 0000000000000..44a1e52ccd244 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [subUrlBase](./kibana-plugin-core-public.legacyapp.suburlbase.md) + +## LegacyApp.subUrlBase property + +Signature: + +```typescript +subUrlBase?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index b2524ec48c757..9e4afe0f5133c 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -90,6 +90,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IHttpResponseInterceptorOverrides](./kibana-plugin-core-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | | [ImageValidation](./kibana-plugin-core-public.imagevalidation.md) | | | [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | +| [LegacyApp](./kibana-plugin-core-public.legacyapp.md) | | | [LegacyCoreSetup](./kibana-plugin-core-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the ui/new_platform module. | | [LegacyCoreStart](./kibana-plugin-core-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform module. | | [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) | | @@ -162,6 +163,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [NavType](./kibana-plugin-core-public.navtype.md) | | | [PluginInitializer](./kibana-plugin-core-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | | [PluginOpaqueId](./kibana-plugin-core-public.pluginopaqueid.md) | | +| [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) | Public information about a registered [application](./kibana-plugin-core-public.app.md) | +| [PublicLegacyAppInfo](./kibana-plugin-core-public.publiclegacyappinfo.md) | Information about a registered [legacy application](./kibana-plugin-core-public.legacyapp.md) | | [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. | | [RecursiveReadonly](./kibana-plugin-core-public.recursivereadonly.md) | | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md new file mode 100644 index 0000000000000..c70f3a97a8882 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) + +## PublicAppInfo type + +Public information about a registered [application](./kibana-plugin-core-public.app.md) + +Signature: + +```typescript +export declare type PublicAppInfo = Omit & { + legacy: false; +}; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md new file mode 100644 index 0000000000000..cc3e9de3193cb --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicLegacyAppInfo](./kibana-plugin-core-public.publiclegacyappinfo.md) + +## PublicLegacyAppInfo type + +Information about a registered [legacy application](./kibana-plugin-core-public.legacyapp.md) + +Signature: + +```typescript +export declare type PublicLegacyAppInfo = Omit & { + legacy: true; +}; +``` diff --git a/docs/images/canvas-add-image.gif b/docs/images/canvas-add-image.gif index a2263e22c4c49..994ec6e1b4f28 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 a1fa228645836..c6e09d6f386ae 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 0000000000000..68a7ca248d9ee 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 0000000000000..ec38b5c1c5f7e 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 d0aa7db375a40..bf5e04bf89af5 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 0c9fbf7201d80..11327224fc897 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 bd0e49377262e..1bfd1132f25c7 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 fa910daf948d7..213bbaa5a26d3 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-fullscreen.png b/docs/images/canvas-fullscreen.png index 7e6ec6ad7e7a8..b8a816d290396 100644 Binary files a/docs/images/canvas-fullscreen.png and b/docs/images/canvas-fullscreen.png differ diff --git a/docs/images/canvas-generate-pdf.gif b/docs/images/canvas-generate-pdf.gif index 9ef16dc1e5017..513f6b3b960f9 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 f869ccb344a46..13c9090e77c76 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 59ef97e0ceae8..c6ba5c29df42a 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 d9735a2fb3e91..03871dcc81862 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 99006a5b8f12d..62e88ad4bf7d0 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 8b8356ff5f7ea..e210b0b3288c6 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-zoom-controls.png b/docs/images/canvas-zoom-controls.png index 892721b627027..5c72d118041e4 100644 Binary files a/docs/images/canvas-zoom-controls.png and b/docs/images/canvas-zoom-controls.png differ diff --git a/docs/images/canvas_element_options.png b/docs/images/canvas_element_options.png index 191348d919b50..41457bab4ff36 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 a63f5135f2a0e..8a601efab874a 100644 Binary files a/docs/images/canvas_save_element.png and b/docs/images/canvas_save_element.png differ diff --git a/docs/index.asciidoc b/docs/index.asciidoc index 5474772ab7da8..add91600a34ea 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -3,11 +3,11 @@ :include-xpack: true :lang: en -:kib-repo-dir: {docdir} +:kib-repo-dir: {kibana-root}/docs :blog-ref: https://www.elastic.co/blog/ :wikipedia: https://en.wikipedia.org/wiki -include::{asciidoc-dir}/../../shared/versions/stack/{source_branch}.asciidoc[] +include::{docs-root}/shared/versions/stack/{source_branch}.asciidoc[] :docker-repo: docker.elastic.co/kibana/kibana :docker-image: docker.elastic.co/kibana/kibana:{version} @@ -18,7 +18,7 @@ include::{asciidoc-dir}/../../shared/versions/stack/{source_branch}.asciidoc[] :blob: {repo}blob/{branch}/ :security-ref: https://www.elastic.co/community/security/ -include::{asciidoc-dir}/../../shared/attributes.asciidoc[] +include::{docs-root}/shared/attributes.asciidoc[] include::user/index.asciidoc[] diff --git a/docs/ingest_manager/index.asciidoc b/docs/ingest_manager/index.asciidoc index 866935d1fa580..1728309f3dfd9 100644 --- a/docs/ingest_manager/index.asciidoc +++ b/docs/ingest_manager/index.asciidoc @@ -110,12 +110,12 @@ fetched by this input should be processed and which Data Stream to send it to. Ingest Management enforces an indexing strategy to allow the system to automatically detect indices and run queries on it. In short the indexing strategy looks as following: ``` -{type}-{dataset}-{namespace} +{dataset.type}-{dataset.name}-{dataset.namespace} ``` -The `{type}` can be `logs` or `metrics`. The `{namespace}` is the part where the user can use free form. The only two requirement are that it has only characters allowed in an Elasticsearch index name and does NOT contain a `-`. The `dataset` is defined by the data that is indexed. The same requirements as for the namespace apply. It is expected that the fields for type, namespace and dataset are part of each event and are constant keywords. If there is a dataset or a namespace with a `-` inside, it is recommended to replace it either by a `.` or a `_`. +The `{dataset.type}` can be `logs` or `metrics`. The `{dataset.namespace}` is the part where the user can use free form. The only two requirement are that it has only characters allowed in an Elasticsearch index name and does NOT contain a `-`. The `dataset` is defined by the data that is indexed. The same requirements as for the namespace apply. It is expected that the fields for type, namespace and dataset are part of each event and are constant keywords. If there is a dataset or a namespace with a `-` inside, it is recommended to replace it either by a `.` or a `_`. -Note: More `{type}`s might be added in the future like `apm` and `endpoint`. +Note: More `{dataset.type}`s might be added in the future like `traces`. This indexing strategy has a few advantages: @@ -133,7 +133,7 @@ Overall it creates smaller indices in size, makes querying more efficient and al The ingest pipelines for a specific dataset will have the following naming scheme: ``` -{type}-{dataset}-{package.version} +{dataset.type}-{dataset.name}-{package.version} ``` As an example, the ingest pipeline for the Nginx access logs is called `logs-nginx.access-3.4.1`. The same ingest pipeline is used for all namespaces. It is possible that a dataset has multiple ingest pipelines in which case a suffix is added to the name. @@ -151,7 +151,7 @@ Each type template contains an ILM policy. Modifying this default ILM policy wil The templates for a dataset are called as following: ``` -{type}-{dataset} +{dataset.type}-{dataset.name} ``` The pattern used inside the index template is `{type}-{dataset}-*` to match all namespaces. diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index 12ee96b21b0c7..e8029ed1bbe9b 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -93,7 +93,7 @@ Some example translations are shown here: [horizontal] **Environment Variable**:: **Kibana Setting** `SERVER_NAME`:: `server.name` -`KIBANA_DEFAULTAPPID`:: `kibana.defaultAppId` +`SERVER_BASEPATH`:: `server.basePath` `MONITORING_ENABLED`:: `monitoring.enabled` In general, any setting listed in <> can be diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 6596f93a88f51..1be9d5b1ef35b 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -216,7 +216,9 @@ on the {kib} index at startup. {kib} users still need to authenticate with | Enables use of interpreter in Visualize. *Default: `true`* | `kibana.defaultAppId:` - | The default application to load. *Default: `"home"`* + | *deprecated* This setting is deprecated and will get removed in Kibana 8.0. +Please use the `defaultRoute` advanced setting instead. +The default application to load. *Default: `"home"`* | `kibana.index:` | {kib} uses an index in {es} to store saved searches, visualizations, and @@ -627,17 +629,17 @@ Valid locales are: `en`, `zh-CN`, `ja-JP`. *Default: `en`* |=== -include::{docdir}/settings/alert-action-settings.asciidoc[] -include::{docdir}/settings/apm-settings.asciidoc[] -include::{docdir}/settings/dev-settings.asciidoc[] -include::{docdir}/settings/graph-settings.asciidoc[] -include::{docdir}/settings/infrastructure-ui-settings.asciidoc[] -include::{docdir}/settings/i18n-settings.asciidoc[] -include::{docdir}/settings/logs-ui-settings.asciidoc[] -include::{docdir}/settings/ml-settings.asciidoc[] -include::{docdir}/settings/monitoring-settings.asciidoc[] -include::{docdir}/settings/reporting-settings.asciidoc[] +include::{kib-repo-dir}/settings/alert-action-settings.asciidoc[] +include::{kib-repo-dir}/settings/apm-settings.asciidoc[] +include::{kib-repo-dir}/settings/dev-settings.asciidoc[] +include::{kib-repo-dir}/settings/graph-settings.asciidoc[] +include::{kib-repo-dir}/settings/infrastructure-ui-settings.asciidoc[] +include::{kib-repo-dir}/settings/i18n-settings.asciidoc[] +include::{kib-repo-dir}/settings/logs-ui-settings.asciidoc[] +include::{kib-repo-dir}/settings/ml-settings.asciidoc[] +include::{kib-repo-dir}/settings/monitoring-settings.asciidoc[] +include::{kib-repo-dir}/settings/reporting-settings.asciidoc[] include::secure-settings.asciidoc[] -include::{docdir}/settings/security-settings.asciidoc[] -include::{docdir}/settings/spaces-settings.asciidoc[] -include::{docdir}/settings/telemetry-settings.asciidoc[] +include::{kib-repo-dir}/settings/security-settings.asciidoc[] +include::{kib-repo-dir}/settings/spaces-settings.asciidoc[] +include::{kib-repo-dir}/settings/telemetry-settings.asciidoc[] diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index 98033c5a87f6f..355684f7448a1 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/examples/alerting_example/kibana.json b/examples/alerting_example/kibana.json index 76c549adc7970..2b6389649cef9 100644 --- a/examples/alerting_example/kibana.json +++ b/examples/alerting_example/kibana.json @@ -4,6 +4,6 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["triggers_actions_ui", "charts", "data", "alerting", "actions"], + "requiredPlugins": ["triggers_actions_ui", "charts", "data", "alerts", "actions"], "optionalPlugins": [] } diff --git a/examples/alerting_example/public/alert_types/astros.tsx b/examples/alerting_example/public/alert_types/astros.tsx index 2e263e454fa0c..d52223cb6b988 100644 --- a/examples/alerting_example/public/alert_types/astros.tsx +++ b/examples/alerting_example/public/alert_types/astros.tsx @@ -32,12 +32,12 @@ import { import { i18n } from '@kbn/i18n'; import { flatten } from 'lodash'; import { ALERTING_EXAMPLE_APP_ID, Craft, Operator } from '../../common/constants'; -import { SanitizedAlert } from '../../../../x-pack/plugins/alerting/common'; -import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerting/public'; +import { SanitizedAlert } from '../../../../x-pack/plugins/alerts/common'; +import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerts/public'; import { AlertTypeModel } from '../../../../x-pack/plugins/triggers_actions_ui/public'; -export function registerNavigation(alerting: AlertingSetup) { - alerting.registerNavigation( +export function registerNavigation(alerts: AlertingSetup) { + alerts.registerNavigation( ALERTING_EXAMPLE_APP_ID, 'example.people-in-space', (alert: SanitizedAlert) => `/astros/${alert.id}` diff --git a/examples/alerting_example/public/alert_types/index.ts b/examples/alerting_example/public/alert_types/index.ts index 96d9c09d15836..db9f855b573e8 100644 --- a/examples/alerting_example/public/alert_types/index.ts +++ b/examples/alerting_example/public/alert_types/index.ts @@ -19,15 +19,15 @@ import { registerNavigation as registerPeopleInSpaceNavigation } from './astros'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; -import { SanitizedAlert } from '../../../../x-pack/plugins/alerting/common'; -import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerting/public'; +import { SanitizedAlert } from '../../../../x-pack/plugins/alerts/common'; +import { PluginSetupContract as AlertingSetup } from '../../../../x-pack/plugins/alerts/public'; -export function registerNavigation(alerting: AlertingSetup) { +export function registerNavigation(alerts: AlertingSetup) { // register default navigation - alerting.registerDefaultNavigation( + alerts.registerDefaultNavigation( ALERTING_EXAMPLE_APP_ID, (alert: SanitizedAlert) => `/alert/${alert.id}` ); - registerPeopleInSpaceNavigation(alerting); + registerPeopleInSpaceNavigation(alerts); } diff --git a/examples/alerting_example/public/components/view_alert.tsx b/examples/alerting_example/public/components/view_alert.tsx index e418ed91096eb..75a515bfa1b25 100644 --- a/examples/alerting_example/public/components/view_alert.tsx +++ b/examples/alerting_example/public/components/view_alert.tsx @@ -36,7 +36,7 @@ import { Alert, AlertTaskState, BASE_ALERT_API_PATH, -} from '../../../../x-pack/plugins/alerting/common'; +} from '../../../../x-pack/plugins/alerts/common'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; type Props = RouteComponentProps & { diff --git a/examples/alerting_example/public/components/view_astros_alert.tsx b/examples/alerting_example/public/components/view_astros_alert.tsx index 296269180dd7b..19f235a3f3e4e 100644 --- a/examples/alerting_example/public/components/view_astros_alert.tsx +++ b/examples/alerting_example/public/components/view_astros_alert.tsx @@ -38,7 +38,7 @@ import { Alert, AlertTaskState, BASE_ALERT_API_PATH, -} from '../../../../x-pack/plugins/alerting/common'; +} from '../../../../x-pack/plugins/alerts/common'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; type Props = RouteComponentProps & { diff --git a/examples/alerting_example/public/plugin.tsx b/examples/alerting_example/public/plugin.tsx index e3748e3235f47..524ff18bd434e 100644 --- a/examples/alerting_example/public/plugin.tsx +++ b/examples/alerting_example/public/plugin.tsx @@ -18,7 +18,7 @@ */ import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; -import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerting/public'; +import { PluginSetupContract as AlertingSetup } from '../../../x-pack/plugins/alerts/public'; import { ChartsPluginStart } from '../../../src/plugins/charts/public'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../x-pack/plugins/triggers_actions_ui/public'; import { DataPublicPluginStart } from '../../../src/plugins/data/public'; @@ -30,12 +30,12 @@ export type Setup = void; export type Start = void; export interface AlertingExamplePublicSetupDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup; } export interface AlertingExamplePublicStartDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup; charts: ChartsPluginStart; data: DataPublicPluginStart; @@ -44,7 +44,7 @@ export interface AlertingExamplePublicStartDeps { export class AlertingExamplePlugin implements Plugin { public setup( core: CoreSetup, - { alerting, triggers_actions_ui }: AlertingExamplePublicSetupDeps + { alerts, triggers_actions_ui }: AlertingExamplePublicSetupDeps ) { core.application.register({ id: 'AlertingExample', @@ -59,7 +59,7 @@ export class AlertingExamplePlugin implements Plugin { - public setup(core: CoreSetup, { alerting }: AlertingExampleDeps) { - alerting.registerType(alwaysFiringAlert); - alerting.registerType(peopleInSpaceAlert); + public setup(core: CoreSetup, { alerts }: AlertingExampleDeps) { + alerts.registerType(alwaysFiringAlert); + alerts.registerType(peopleInSpaceAlert); } public start() {} diff --git a/package.json b/package.json index cc1f7eb6c1dd3..1201a1773e6cd 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "@elastic/eui": "23.3.1", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", - "@elastic/numeral": "2.4.0", + "@elastic/numeral": "^2.5.0", "@elastic/request-crypto": "1.1.4", "@elastic/ui-ace": "0.2.3", "@hapi/good-squeeze": "5.2.1", @@ -365,7 +365,6 @@ "@types/node": ">=10.17.17 <10.20.0", "@types/node-forge": "^0.9.0", "@types/normalize-path": "^3.0.0", - "@types/numeral": "^0.0.26", "@types/opn": "^5.1.0", "@types/pegjs": "^0.10.1", "@types/pngjs": "^3.3.2", diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/eslint-config-kibana/.eslintrc.js index 624ee4679a3b9..747c2c14ab25e 100644 --- a/packages/eslint-config-kibana/.eslintrc.js +++ b/packages/eslint-config-kibana/.eslintrc.js @@ -39,6 +39,10 @@ module.exports = { to: false, disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead` }, + { + from: 'numeral', + to: '@elastic/numeral', + }, { from: '@kbn/elastic-idx', to: false, diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts index 4ed241f3b9b2e..37d8a4f5eb8ae 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts @@ -152,7 +152,7 @@ export class OptimizerConfig { new Bundle({ type: 'entry', id: 'core', - entry: './public/entry_point', + entry: './public/index', sourceRoot: options.repoRoot, contextDir: Path.resolve(options.repoRoot, 'src/core'), outputDir: Path.resolve(options.repoRoot, 'src/core/target/public'), diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 763f1d515804f..dd003af7dc5e9 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -38,11 +38,32 @@ const IS_CODE_COVERAGE = !!process.env.CODE_COVERAGE; const ISTANBUL_PRESET_PATH = require.resolve('@kbn/babel-preset/istanbul_preset'); const BABEL_PRESET_PATH = require.resolve('@kbn/babel-preset/webpack_preset'); -const STATIC_BUNDLE_PLUGINS = [ - { id: 'data', dirname: 'data' }, - { id: 'kibanaReact', dirname: 'kibana_react' }, - { id: 'kibanaUtils', dirname: 'kibana_utils' }, - { id: 'esUiShared', dirname: 'es_ui_shared' }, +const SHARED_BUNDLES = [ + { + type: 'entry', + id: 'core', + rootRelativeDir: 'src/core/public', + }, + { + type: 'plugin', + id: 'data', + rootRelativeDir: 'src/plugins/data/public', + }, + { + type: 'plugin', + id: 'kibanaReact', + rootRelativeDir: 'src/plugins/kibana_react/public', + }, + { + type: 'plugin', + id: 'kibanaUtils', + rootRelativeDir: 'src/plugins/kibana_utils/public', + }, + { + type: 'plugin', + id: 'esUiShared', + rootRelativeDir: 'src/plugins/es_ui_shared/public', + }, ]; /** @@ -57,18 +78,8 @@ const STATIC_BUNDLE_PLUGINS = [ * @param request the request for a module from a commonjs require() call or import statement */ function dynamicExternals(bundle: Bundle, context: string, request: string) { - // ignore imports that have loaders defined - if (request.includes('!')) { - return; - } - - // ignore requests that don't include a /{dirname}/public for one of our - // "static" bundles as a cheap way to avoid doing path resolution - // for paths that couldn't possibly resolve to what we're looking for - const reqToStaticBundle = STATIC_BUNDLE_PLUGINS.some((p) => - request.includes(`/${p.dirname}/public`) - ); - if (!reqToStaticBundle) { + // ignore imports that have loaders defined or are not relative seeming + if (request.includes('!') || !request.startsWith('.')) { return; } @@ -76,10 +87,15 @@ function dynamicExternals(bundle: Bundle, context: string, request: string) { const rootRelative = normalizePath( Path.relative(bundle.sourceRoot, Path.resolve(context, request)) ); - for (const { id, dirname } of STATIC_BUNDLE_PLUGINS) { - if (rootRelative === `src/plugins/${dirname}/public`) { - return `__kbnBundles__['plugin/${id}']`; + for (const sharedBundle of SHARED_BUNDLES) { + if ( + rootRelative !== sharedBundle.rootRelativeDir || + `${bundle.type}/${bundle.id}` === `${sharedBundle.type}/${sharedBundle.id}` + ) { + continue; } + + return `__kbnBundles__['${sharedBundle.type}/${sharedBundle.id}']`; } // import doesn't match a root public import @@ -112,13 +128,9 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { info.absoluteResourcePath )}${info.query}`, jsonpFunction: `${bundle.id}_bundle_jsonpfunction`, - ...(bundle.type === 'plugin' - ? { - // When the entry point is loaded, assign it's exported `plugin` - // value to a key on the global `__kbnBundles__` object. - library: ['__kbnBundles__', `plugin/${bundle.id}`], - } - : {}), + // When the entry point is loaded, assign it's default export + // to a key on the global `__kbnBundles__` object. + library: ['__kbnBundles__', `${bundle.type}/${bundle.id}`], }, optimization: { diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/entry.js index 26efd174f4e39..ab044a6723da7 100644 --- a/packages/kbn-ui-shared-deps/entry.js +++ b/packages/kbn-ui-shared-deps/entry.js @@ -44,6 +44,7 @@ Moment.tz.load(require('moment-timezone/data/packed/latest.json')); // big deps which are locked to a single version export const Rxjs = require('rxjs'); export const RxjsOperators = require('rxjs/operators'); +export const ElasticNumeral = require('@elastic/numeral'); export const ElasticCharts = require('@elastic/charts'); export const ElasticEui = require('@elastic/eui'); export const ElasticEuiLibServices = require('@elastic/eui/lib/services'); diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index 9aec3ab359924..eb3add68e2866 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -51,6 +51,8 @@ exports.externals = { */ rxjs: '__kbnSharedDeps__.Rxjs', 'rxjs/operators': '__kbnSharedDeps__.RxjsOperators', + numeral: '__kbnSharedDeps__.ElasticNumeral', + '@elastic/numeral': '__kbnSharedDeps__.ElasticNumeral', '@elastic/charts': '__kbnSharedDeps__.ElasticCharts', '@elastic/eui': '__kbnSharedDeps__.ElasticEui', '@elastic/eui/lib/services': '__kbnSharedDeps__.ElasticEuiLibServices', diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 4e6bec92a65e4..93afa303c8cad 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -11,6 +11,7 @@ "dependencies": { "@elastic/charts": "19.2.0", "@elastic/eui": "23.3.1", + "@elastic/numeral": "^2.5.0", "@kbn/i18n": "1.0.0", "abortcontroller-polyfill": "^1.4.0", "angular": "^1.7.9", diff --git a/renovate.json5 b/renovate.json5 index 9a2ac20f91f04..674c4e0df7904 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -715,14 +715,6 @@ '@types/normalize-path', ], }, - { - groupSlug: 'numeral', - groupName: 'numeral related packages', - packageNames: [ - 'numeral', - '@types/numeral', - ], - }, { groupSlug: 'object-hash', groupName: 'object-hash related packages', diff --git a/rfcs/text/0003_handler_interface.md b/rfcs/text/0003_handler_interface.md index 51e78cf7c9f54..197f2a2012376 100644 --- a/rfcs/text/0003_handler_interface.md +++ b/rfcs/text/0003_handler_interface.md @@ -65,7 +65,7 @@ application.registerApp({ }); // Alerting -alerting.registerType({ +alerts.registerType({ id: 'myAlert', async execute(context, params, state) { const indexPatterns = await context.core.savedObjects.find('indexPattern'); diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index 447c6f396945f..a4f50e73f1c57 100644 --- a/src/core/CONVENTIONS.md +++ b/src/core/CONVENTIONS.md @@ -167,17 +167,21 @@ leverage this pattern. import React from 'react'; import ReactDOM from 'react-dom'; -import { CoreStart, AppMountParams } from '../../src/core/public'; +import { CoreStart, AppMountParameters } from 'src/core/public'; import { MyAppRoot } from './components/app.ts'; /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. */ -export const renderApp = (core: CoreStart, deps: MyPluginDepsStart, { element, history }: AppMountParams) => { +export const renderApp = ( + core: CoreStart, + deps: MyPluginDepsStart, + { element, history }: AppMountParameters +) => { ReactDOM.render(, element); return () => ReactDOM.unmountComponentAtNode(element); -} +}; ``` ```ts @@ -332,7 +336,7 @@ import { SavedObjectsType } from 'src/core/server'; export const myType: SavedObjectsType = { name: 'my-type', hidden: false, - namespaceAgnostic: true, + namespaceType: 'single', mappings: { properties: { someField: { diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index 5cec20fb900f2..6bb5a845ea2ab 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -850,7 +850,7 @@ import { SavedObjectsType } from 'src/core/server'; export const firstType: SavedObjectsType = { name: 'first-type', hidden: false, - namespaceAgnostic: true, + namespaceType: 'agnostic', mappings: { properties: { someField: { @@ -888,7 +888,7 @@ import { SavedObjectsType } from 'src/core/server'; export const secondType: SavedObjectsType = { name: 'second-type', hidden: true, - namespaceAgnostic: false, + namespaceType: 'single', mappings: { properties: { textField: { @@ -936,7 +936,7 @@ export class MyPlugin implements Plugin { The NP `registerType` expected input is very close to the legacy format. However, there are some minor changes: -- The `schema.isNamespaceAgnostic` property has been renamed: `SavedObjectsType.namespaceAgnostic` +- The `schema.isNamespaceAgnostic` property has been renamed: `SavedObjectsType.namespaceType`. It no longer accepts a boolean but instead an enum of 'single', 'multiple', or 'agnostic' (see [SavedObjectsNamespaceType](/docs/development/core/server/kibana-plugin-core-server.savedobjectsnamespacetype.md)). - The `schema.indexPattern` was accepting either a `string` or a `(config: LegacyConfig) => string`. `SavedObjectsType.indexPattern` only accepts a string, as you can access the configuration during your plugin's setup phase. diff --git a/src/core/TESTING.md b/src/core/TESTING.md index cb38dac0e20ce..bed41ab583496 100644 --- a/src/core/TESTING.md +++ b/src/core/TESTING.md @@ -475,10 +475,14 @@ The more interesting logic is in `renderApp`: import React from 'react'; import ReactDOM from 'react-dom'; -import { AppMountParams, CoreStart } from 'src/core/public'; +import { AppMountParameters, CoreStart } from 'src/core/public'; import { AppRoot } from './components/app_root'; -export const renderApp = ({ element, history }: AppMountParams, core: CoreStart, plugins: MyPluginDepsStart) => { +export const renderApp = ( + { element, history }: AppMountParameters, + core: CoreStart, + plugins: MyPluginDepsStart +) => { // Hide the chrome while this app is mounted for a full screen experience core.chrome.setIsVisible(false); diff --git a/src/core/public/application/application_service.mock.ts b/src/core/public/application/application_service.mock.ts index 24c0e66359afa..300b09e17d15d 100644 --- a/src/core/public/application/application_service.mock.ts +++ b/src/core/public/application/application_service.mock.ts @@ -25,8 +25,8 @@ import { InternalApplicationStart, ApplicationStart, InternalApplicationSetup, - App, - LegacyApp, + PublicAppInfo, + PublicLegacyAppInfo, } from './types'; import { ApplicationServiceContract } from './test_types'; @@ -47,6 +47,7 @@ const createStartContractMock = (): jest.Mocked => { const currentAppId$ = new Subject(); return { + applications$: new BehaviorSubject>(new Map()), currentAppId$: currentAppId$.asObservable(), capabilities: capabilitiesServiceMock.createStartContract().capabilities, navigateToApp: jest.fn(), @@ -60,7 +61,7 @@ const createInternalStartContractMock = (): jest.Mocked(); return { - applications$: new BehaviorSubject>(new Map()), + applications$: new BehaviorSubject>(new Map()), capabilities: capabilitiesServiceMock.createStartContract().capabilities, currentAppId$: currentAppId$.asObservable(), getComponent: jest.fn(), diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index b65a8581e5b58..400d1881a5af8 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -34,7 +34,15 @@ import { httpServiceMock } from '../http/http_service.mock'; import { overlayServiceMock } from '../overlays/overlay_service.mock'; import { MockLifecycle } from './test_types'; import { ApplicationService } from './application_service'; -import { App, AppNavLinkStatus, AppStatus, AppUpdater, LegacyApp } from './types'; +import { + App, + PublicAppInfo, + AppNavLinkStatus, + AppStatus, + AppUpdater, + LegacyApp, + PublicLegacyAppInfo, +} from './types'; import { act } from 'react-dom/test-utils'; const createApp = (props: Partial): App => { @@ -366,7 +374,10 @@ describe('#setup()', () => { setup.registerAppUpdater(statusUpdater); const start = await service.start(startDeps); - let latestValue: ReadonlyMap = new Map(); + let latestValue: ReadonlyMap = new Map< + string, + PublicAppInfo | PublicLegacyAppInfo + >(); start.applications$.subscribe((apps) => { latestValue = apps; }); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index b52b4984fb5e1..2224f72e2bd91 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -46,7 +46,7 @@ import { Mounter, } from './types'; import { getLeaveAction, isConfirmAction } from './application_leave'; -import { appendAppPath, parseAppUrl, relativeToAbsolute } from './utils'; +import { appendAppPath, parseAppUrl, relativeToAbsolute, getAppInfo } from './utils'; interface SetupDeps { context: ContextSetup; @@ -291,7 +291,10 @@ export class ApplicationService { }; return { - applications$, + applications$: applications$.pipe( + map((apps) => new Map([...apps.entries()].map(([id, app]) => [id, getAppInfo(app)]))), + shareReplay(1) + ), capabilities, currentAppId$: this.currentAppId$.pipe( filter((appId) => appId !== undefined), diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index ec10d2bc22871..d51a4c0d69d42 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -39,7 +39,9 @@ export { AppLeaveAction, AppLeaveDefaultAction, AppLeaveConfirmAction, + LegacyApp, + PublicAppInfo, + PublicLegacyAppInfo, // Internal types InternalApplicationStart, - LegacyApp, } from './types'; diff --git a/src/core/public/application/scoped_history.ts b/src/core/public/application/scoped_history.ts index 1a7fafa5d85c4..4392cf4eca8d4 100644 --- a/src/core/public/application/scoped_history.ts +++ b/src/core/public/application/scoped_history.ts @@ -197,7 +197,7 @@ export class ScopedHistory prompt?: boolean | string | TransitionPromptHook ): UnregisterCallback => { throw new Error( - `history.block is not supported. Please use the AppMountParams.onAppLeave API.` + `history.block is not supported. Please use the AppMountParameters.onAppLeave API.` ); }; diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index c07d929fc5cea..8006ec846138f 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -235,7 +235,7 @@ export interface App extends AppBase { appRoute?: string; } -/** @internal */ +/** @public */ export interface LegacyApp extends AppBase { appUrl: string; subUrlBase?: string; @@ -243,6 +243,24 @@ export interface LegacyApp extends AppBase { disableSubUrlTracking?: boolean; } +/** + * Public information about a registered {@link App | application} + * + * @public + */ +export type PublicAppInfo = Omit & { + legacy: false; +}; + +/** + * Information about a registered {@link LegacyApp | legacy application} + * + * @public + */ +export type PublicLegacyAppInfo = Omit & { + legacy: true; +}; + /** * A mount function called when the user navigates to this app's route. * @@ -435,10 +453,10 @@ export interface AppMountParameters { * import ReactDOM from 'react-dom'; * import { BrowserRouter, Route } from 'react-router-dom'; * - * import { CoreStart, AppMountParams } from 'src/core/public'; + * import { CoreStart, AppMountParameters } from 'src/core/public'; * import { MyPluginDepsStart } from './plugin'; * - * export renderApp = ({ element, history, onAppLeave }: AppMountParams) => { + * export renderApp = ({ element, history, onAppLeave }: AppMountParameters) => { * const { renderApp, hasUnsavedChanges } = await import('./application'); * onAppLeave(actions => { * if(hasUnsavedChanges()) { @@ -649,6 +667,15 @@ export interface ApplicationStart { */ capabilities: RecursiveReadonly; + /** + * Observable emitting the list of currently registered apps and their associated status. + * + * @remarks + * Applications disabled by {@link Capabilities} will not be present in the map. Applications manually disabled from + * the client-side using an {@link AppUpdater | application updater} are present, with their status properly set as `inaccessible`. + */ + applications$: Observable>; + /** * Navigate to a given app * @@ -721,18 +748,7 @@ export interface ApplicationStart { } /** @internal */ -export interface InternalApplicationStart - extends Pick< - ApplicationStart, - 'capabilities' | 'navigateToApp' | 'navigateToUrl' | 'getUrlForApp' | 'currentAppId$' - > { - /** - * Apps available based on the current capabilities. - * Should be used to show navigation links and make routing decisions. - * Applications manually disabled from the client-side using {@link AppUpdater} - */ - applications$: Observable>; - +export interface InternalApplicationStart extends Omit { /** * Register a context provider for application mounting. Will only be available to applications that depend on the * plugin that registered this context. Deprecated, use {@link CoreSetup.getStartServices}. diff --git a/src/core/public/application/utils.test.ts b/src/core/public/application/utils.test.ts index a86a1206fc983..b41945aa43682 100644 --- a/src/core/public/application/utils.test.ts +++ b/src/core/public/application/utils.test.ts @@ -17,7 +17,8 @@ * under the License. */ -import { LegacyApp, App } from './types'; +import { of } from 'rxjs'; +import { LegacyApp, App, AppStatus, AppNavLinkStatus } from './types'; import { BasePath } from '../http/base_path'; import { removeSlashes, @@ -25,6 +26,7 @@ import { isLegacyApp, relativeToAbsolute, parseAppUrl, + getAppInfo, } from './utils'; describe('removeSlashes', () => { @@ -459,3 +461,56 @@ describe('parseAppUrl', () => { }); }); }); + +describe('getAppInfo', () => { + const createApp = (props: Partial = {}): App => ({ + mount: () => () => undefined, + updater$: of(() => undefined), + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + appRoute: `/app/some-id`, + legacy: false, + ...props, + }); + + const createLegacyApp = (props: Partial = {}): LegacyApp => ({ + appUrl: '/my-app-url', + updater$: of(() => undefined), + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + legacy: true, + ...props, + }); + + it('converts an application and remove sensitive properties', () => { + const app = createApp(); + const info = getAppInfo(app); + + expect(info).toEqual({ + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + appRoute: `/app/some-id`, + legacy: false, + }); + }); + + it('converts a legacy application and remove sensitive properties', () => { + const app = createLegacyApp(); + const info = getAppInfo(app); + + expect(info).toEqual({ + appUrl: '/my-app-url', + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + legacy: true, + }); + }); +}); diff --git a/src/core/public/application/utils.ts b/src/core/public/application/utils.ts index 8987a9402f2db..1abd710548745 100644 --- a/src/core/public/application/utils.ts +++ b/src/core/public/application/utils.ts @@ -18,7 +18,7 @@ */ import { IBasePath } from '../http'; -import { App, LegacyApp } from './types'; +import { App, LegacyApp, PublicAppInfo, PublicLegacyAppInfo } from './types'; export interface AppUrlInfo { app: string; @@ -119,3 +119,19 @@ const removeBasePath = (url: string, basePath: IBasePath, origin: string): strin } return basePath.remove(url); }; + +export function getAppInfo(app: App | LegacyApp): PublicAppInfo | PublicLegacyAppInfo { + if (isLegacyApp(app)) { + const { updater$, ...infos } = app; + return { + ...infos, + legacy: true, + }; + } else { + const { updater$, mount, ...infos } = app; + return { + ...infos, + legacy: false, + }; + } +} diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index 0bc305ed9e28c..e39733cc10de7 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -21,7 +21,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import * as Rx from 'rxjs'; import { take, toArray } from 'rxjs/operators'; -import { App } from '../application'; +import { App, PublicAppInfo } from '../application'; import { applicationServiceMock } from '../application/application_service.mock'; import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; @@ -29,6 +29,7 @@ import { injectedMetadataServiceMock } from '../injected_metadata/injected_metad import { notificationServiceMock } from '../notifications/notifications_service.mock'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; import { ChromeService } from './chrome_service'; +import { getAppInfo } from '../application/utils'; class FakeApp implements App { public title = `${this.id} App`; @@ -55,8 +56,8 @@ function defaultStartDeps(availableApps?: App[]) { }; if (availableApps) { - deps.application.applications$ = new Rx.BehaviorSubject>( - new Map(availableApps.map((app) => [app.id, app])) + deps.application.applications$ = new Rx.BehaviorSubject>( + new Map(availableApps.map((app) => [app.id, getAppInfo(app) as PublicAppInfo])) ); } diff --git a/src/core/public/chrome/nav_links/to_nav_link.test.ts b/src/core/public/chrome/nav_links/to_nav_link.test.ts index 4c319873af804..ba04dbed49cd4 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.test.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.test.ts @@ -17,15 +17,12 @@ * under the License. */ -import { App, AppMount, AppNavLinkStatus, AppStatus, LegacyApp } from '../../application'; +import { PublicAppInfo, AppNavLinkStatus, AppStatus, PublicLegacyAppInfo } from '../../application'; import { toNavLink } from './to_nav_link'; import { httpServiceMock } from '../../mocks'; -function mount() {} - -const app = (props: Partial = {}): App => ({ - mount: (mount as unknown) as AppMount, +const app = (props: Partial = {}): PublicAppInfo => ({ id: 'some-id', title: 'some-title', status: AppStatus.accessible, @@ -35,7 +32,7 @@ const app = (props: Partial = {}): App => ({ ...props, }); -const legacyApp = (props: Partial = {}): LegacyApp => ({ +const legacyApp = (props: Partial = {}): PublicLegacyAppInfo => ({ appUrl: '/my-app-url', id: 'some-id', title: 'some-title', diff --git a/src/core/public/chrome/nav_links/to_nav_link.ts b/src/core/public/chrome/nav_links/to_nav_link.ts index 24744fe53c82c..2dedbfd5f36ac 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.ts @@ -17,12 +17,15 @@ * under the License. */ -import { App, AppNavLinkStatus, AppStatus, LegacyApp } from '../../application'; +import { PublicAppInfo, AppNavLinkStatus, AppStatus, PublicLegacyAppInfo } from '../../application'; import { IBasePath } from '../../http'; import { NavLinkWrapper } from './nav_link'; import { appendAppPath } from '../../application/utils'; -export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWrapper { +export function toNavLink( + app: PublicAppInfo | PublicLegacyAppInfo, + basePath: IBasePath +): NavLinkWrapper { const useAppStatus = app.navLinkStatus === AppNavLinkStatus.default; const relativeBaseUrl = isLegacyApp(app) ? basePath.prepend(app.appUrl) @@ -39,9 +42,7 @@ export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWra legacy: isLegacyApp(app), baseUrl, ...(isLegacyApp(app) - ? { - href: url && !url.startsWith(app.subUrlBase!) ? url : baseUrl, - } + ? {} : { href: url, url, @@ -63,6 +64,6 @@ export function relativeToAbsolute(url: string) { return a.href; } -function isLegacyApp(app: App | LegacyApp): app is LegacyApp { +function isLegacyApp(app: PublicAppInfo | PublicLegacyAppInfo): app is PublicLegacyAppInfo { return app.legacy === true; } diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index c09b15fac9bdb..969b6728e0263 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -55,7 +55,12 @@ export function createEuiListItem({ navigateToApp, dataTestSubj, }: Props) { - const { legacy, active, id, title, disabled, euiIconType, icon, tooltip, href } = link; + const { legacy, active, id, title, disabled, euiIconType, icon, tooltip } = link; + let { href } = link; + + if (legacy) { + href = link.url && !active ? link.url : link.baseUrl; + } return { label: tooltip ?? title, diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 3698fdcfe9512..bd275ca1d4565 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -35,6 +35,8 @@ * @packageDocumentation */ +import './index.scss'; + import { ChromeBadge, ChromeBrand, @@ -104,6 +106,7 @@ export { ApplicationSetup, ApplicationStart, App, + PublicAppInfo, AppBase, AppMount, AppMountDeprecated, @@ -120,6 +123,8 @@ export { AppUpdatableFields, AppUpdater, ScopedHistory, + LegacyApp, + PublicLegacyAppInfo, } from './application'; export { @@ -360,3 +365,5 @@ export { UiSettingsState, NavType, }; + +export { __kbnBootstrap__ } from './kbn_bootstrap'; diff --git a/src/core/public/entry_point.ts b/src/core/public/kbn_bootstrap.ts similarity index 51% rename from src/core/public/entry_point.ts rename to src/core/public/kbn_bootstrap.ts index 25180c13ccbd4..caeb95a540de3 100644 --- a/src/core/public/entry_point.ts +++ b/src/core/public/kbn_bootstrap.ts @@ -25,39 +25,41 @@ * src/legacy/ui/ui_bundles/app_entry_template.js */ -import './index.scss'; import { i18n } from '@kbn/i18n'; import { CoreSystem } from './core_system'; -const injectedMetadata = JSON.parse( - document.querySelector('kbn-injected-metadata')!.getAttribute('data')! -); +/** @internal */ +export function __kbnBootstrap__() { + const injectedMetadata = JSON.parse( + document.querySelector('kbn-injected-metadata')!.getAttribute('data')! + ); -/** - * `apmConfig` would be populated with relavant APM RUM agent - * configuration if server is started with `ELASTIC_APM_ACTIVE=true` - */ -if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true' && injectedMetadata.vars.apmConfig != null) { - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { init } = require('@elastic/apm-rum'); - init(injectedMetadata.vars.apmConfig); -} + /** + * `apmConfig` would be populated with relavant APM RUM agent + * configuration if server is started with `ELASTIC_APM_ACTIVE=true` + */ + if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true' && injectedMetadata.vars.apmConfig != null) { + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { init } = require('@elastic/apm-rum'); + init(injectedMetadata.vars.apmConfig); + } -i18n - .load(injectedMetadata.i18n.translationsUrl) - .catch((e) => e) - .then(async (i18nError) => { - const coreSystem = new CoreSystem({ - injectedMetadata, - rootDomElement: document.body, - browserSupportsCsp: !(window as any).__kbnCspNotEnforced__, - }); + i18n + .load(injectedMetadata.i18n.translationsUrl) + .catch((e) => e) + .then(async (i18nError) => { + const coreSystem = new CoreSystem({ + injectedMetadata, + rootDomElement: document.body, + browserSupportsCsp: !(window as any).__kbnCspNotEnforced__, + }); - const setup = await coreSystem.setup(); - if (i18nError && setup) { - setup.fatalErrors.add(i18nError); - } + const setup = await coreSystem.setup(); + if (i18nError && setup) { + setup.fatalErrors.add(i18nError); + } - await coreSystem.start(); - }); + await coreSystem.start(); + }); +} diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index 810416cdbfe16..d77676b350f93 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -131,6 +131,7 @@ export class LegacyPlatformService { const legacyCore: LegacyCoreStart = { ...core, application: { + applications$: core.application.applications$, currentAppId$: core.application.currentAppId$, capabilities: core.application.capabilities, getUrlForApp: core.application.getUrlForApp, diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index c688373630a07..65c6b6ce4edba 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -135,6 +135,7 @@ export function createPluginStartContext< ): CoreStart { return { application: { + applications$: deps.application.applications$, currentAppId$: deps.application.currentAppId$, capabilities: deps.application.capabilities, navigateToApp: deps.application.navigateToApp, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 90c5dbb5f6558..74c41d010ca8d 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -25,6 +25,9 @@ import { Type } from '@kbn/config-schema'; import { UnregisterCallback } from 'history'; import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; +// @internal (undocumented) +export function __kbnBootstrap__(): void; + // @public export interface App extends AppBase { appRoute?: string; @@ -106,6 +109,7 @@ export interface ApplicationSetup { // @public (undocumented) export interface ApplicationStart { + applications$: Observable>; capabilities: RecursiveReadonly; currentAppId$: Observable; getUrlForApp(appId: string, options?: { @@ -857,6 +861,18 @@ export interface IUiSettingsClient { set: (key: string, value: any) => Promise; } +// @public (undocumented) +export interface LegacyApp extends AppBase { + // (undocumented) + appUrl: string; + // (undocumented) + disableSubUrlTracking?: boolean; + // (undocumented) + linkToLastSubUrl?: boolean; + // (undocumented) + subUrlBase?: string; +} + // @public @deprecated export interface LegacyCoreSetup extends CoreSetup { // Warning: (ae-forgotten-export) The symbol "InjectedMetadataSetup" needs to be exported by the entry point index.d.ts @@ -993,6 +1009,16 @@ export interface PluginInitializerContext // @public (undocumented) export type PluginOpaqueId = symbol; +// @public +export type PublicAppInfo = Omit & { + legacy: false; +}; + +// @public +export type PublicLegacyAppInfo = Omit & { + legacy: true; +}; + // @public export type PublicUiSettingsParams = Omit; diff --git a/src/core/server/ui_settings/saved_objects/ui_settings.ts b/src/core/server/ui_settings/saved_objects/ui_settings.ts index 1bea65ddee924..0eab40a7b3a5d 100644 --- a/src/core/server/ui_settings/saved_objects/ui_settings.ts +++ b/src/core/server/ui_settings/saved_objects/ui_settings.ts @@ -38,7 +38,7 @@ export const uiSettingsType: SavedObjectsType = { importableAndExportable: true, getInAppUrl() { return { - path: `/app/kibana#/management/kibana/settings`, + path: `/app/management/kibana/settings`, uiCapabilitiesPath: 'advancedSettings.show', }; }, diff --git a/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js b/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js deleted file mode 100644 index 03126d130e984..0000000000000 --- a/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import execa from 'execa'; -import expect from '@kbn/expect'; - -const ROOT_DIR = resolve(__dirname, '../../../../..'); -const MOCKS_DIR = resolve(__dirname, './mocks'); -const env = { - BUILD_ID: 407, - CI_RUN_URL: 'https://kibana-ci.elastic.co/job/elastic+kibana+code-coverage/407/', - STATIC_SITE_URL_BASE: 'https://kibana-coverage.elastic.dev', - TIME_STAMP: '2020-03-02T21:11:47Z', - ES_HOST: 'https://super:changeme@some.fake.host:9243', - NODE_ENV: 'integration_test', - COVERAGE_INGESTION_KIBANA_ROOT: '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana', -}; -const verboseArgs = [ - 'scripts/ingest_coverage.js', - '--verbose', - '--vcsInfoPath', - 'src/dev/code_coverage/ingest_coverage/integration_tests/mocks/VCS_INFO.txt', - '--path', -]; - -// FLAKY: https://github.com/elastic/kibana/issues/67554 -// FLAKY: https://github.com/elastic/kibana/issues/67555 -// FLAKY: https://github.com/elastic/kibana/issues/67556 -describe.skip('Ingesting coverage', () => { - const summaryPath = 'jest-combined/coverage-summary-manual-mix.json'; - const resolved = resolve(MOCKS_DIR, summaryPath); - const siteUrlRegex = /"staticSiteUrl": (".+",)/; - let actualUrl = ''; - - beforeAll(async () => { - const opts = [...verboseArgs, resolved]; - const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env }); - actualUrl = siteUrlRegex.exec(stdout)[1]; - }); - - describe(`staticSiteUrl`, () => { - it('should contain the static host', () => { - const staticHost = /https:\/\/kibana-coverage\.elastic\.dev/; - expect(staticHost.test(actualUrl)).ok(); - }); - it('should contain the timestamp', () => { - const timeStamp = /\d{4}-\d{2}-\d{2}T\d{2}.*\d{2}.*\d{2}Z/; - expect(timeStamp.test(actualUrl)).ok(); - }); - it('should contain the folder structure', () => { - const folderStructure = /(?:.*|.*-combined)\//; - expect(folderStructure.test(actualUrl)).ok(); - }); - }); -}); diff --git a/src/dev/code_coverage/shell_scripts/fix_html_reports_parallel.sh b/src/dev/code_coverage/shell_scripts/fix_html_reports_parallel.sh index bc184301a6831..098737eb2f800 100644 --- a/src/dev/code_coverage/shell_scripts/fix_html_reports_parallel.sh +++ b/src/dev/code_coverage/shell_scripts/fix_html_reports_parallel.sh @@ -7,7 +7,13 @@ COMBINED_EXRACT_DIR=/${EXTRACT_START_DIR}/${EXTRACT_END_DIR} PWD=$(pwd) du -sh $COMBINED_EXRACT_DIR -echo "### Replacing path in json files" +echo "### Jest: replacing path in json files" +for i in coverage-final xpack-coverage-final; do + sed -i "s|/dev/shm/workspace/kibana|${PWD}|g" $COMBINED_EXRACT_DIR/jest/${i}.json & +done +wait + +echo "### Functional: replacing path in json files" for i in {1..9}; do sed -i "s|/dev/shm/workspace/kibana|${PWD}|g" $COMBINED_EXRACT_DIR/functional/${i}*.json & done diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 2eee3b2c53bd3..d968a365e7bb9 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -64,6 +64,8 @@ export const IGNORE_FILE_GLOBS = [ 'x-pack/plugins/apm/public/**/*', 'x-pack/plugins/apm/scripts/**/*', 'x-pack/plugins/apm/e2e/**/*', + + 'x-pack/plugins/maps/server/fonts/**/*', ]; /** diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts index 1417d30484678..5d4cf14c1cd95 100644 --- a/src/dev/typescript/run_type_check_cli.ts +++ b/src/dev/typescript/run_type_check_cli.ts @@ -88,7 +88,7 @@ export function runTypeCheckCli() { } execInProjects(log, projects, process.execPath, (project) => [ - ...(project.name === 'x-pack' ? ['--max-old-space-size=4096'] : []), + ...(project.name.startsWith('x-pack') ? ['--max-old-space-size=4096'] : []), require.resolve('typescript/bin/tsc'), ...['--project', project.tsConfigPath], ...tscArgs, diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index ef56ae0e2380c..ae613e0e80904 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -26,8 +26,7 @@ import { exportApi } from './server/routes/api/export'; import { getUiSettingDefaults } from './server/ui_setting_defaults'; import { registerCspCollector } from './server/lib/csp_usage_collector'; import { injectVars } from './inject_vars'; -import { i18n } from '@kbn/i18n'; -import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; + import { kbnBaseUrl } from '../../../plugins/kibana_legacy/server'; const mkdirAsync = promisify(Fs.mkdir); @@ -53,19 +52,7 @@ export default function (kibana) { main: 'plugins/kibana/kibana', }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), - links: [ - { - id: 'kibana:stack_management', - title: i18n.translate('kbn.managementTitle', { - defaultMessage: 'Stack Management', - }), - order: 9003, - url: `${kbnBaseUrl}#/management`, - euiIconType: 'managementApp', - linkToLastSubUrl: false, - category: DEFAULT_APP_CATEGORIES.management, - }, - ], + links: [], injectDefaultVars(server, options) { const mapConfig = server.config().get('map'); diff --git a/src/legacy/core_plugins/kibana/public/.eslintrc.js b/src/legacy/core_plugins/kibana/public/.eslintrc.js deleted file mode 100644 index 1153706eb8566..0000000000000 --- a/src/legacy/core_plugins/kibana/public/.eslintrc.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const topLevelConfig = require('../../../../../.eslintrc.js'); -const path = require('path'); - -const topLevelRestricedZones = topLevelConfig.overrides.find( - override => - override.files[0] === '**/*.{js,ts,tsx}' && - Object.keys(override.rules)[0] === '@kbn/eslint/no-restricted-paths' -).rules['@kbn/eslint/no-restricted-paths'][1].zones; - -/** - * Builds custom restricted paths configuration for the shimmed plugins within the kibana plugin. - * These custom rules extend the default checks in the top level `eslintrc.js` by also checking two other things: - * * Making sure nothing within np_ready imports from the `ui` directory - * * Making sure no other code is importing things deep from within the shimmed plugins - * @param shimmedPlugins List of plugin names within the kibana plugin that are partially np ready - * @returns zones configuration for the no-restricted-paths linter - */ -function buildRestrictedPaths(shimmedPlugins) { - return shimmedPlugins - .map(shimmedPlugin => [ - { - target: [`src/legacy/core_plugins/kibana/public/${shimmedPlugin}/np_ready/**/*`], - from: [ - 'ui/**/*', - 'src/legacy/ui/**/*', - 'src/legacy/core_plugins/kibana/public/**/*', - `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, - ], - allowSameFolder: false, - errorMessage: `${shimmedPlugin} is a shimmed plugin that is not allowed to import modules from the legacy platform. If you need legacy modules for the transition period, import them either in the legacy_imports, kibana_services or index module.`, - }, - { - target: [ - 'src/**/*', - `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, - 'x-pack/**/*', - ], - from: [ - `src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, - `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/index.ts`, - `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/legacy.ts`, - ], - allowSameFolder: false, - errorMessage: `kibana/public/${shimmedPlugin} is behaving like a NP plugin and does not allow deep imports. If you need something from within ${shimmedPlugin} in another plugin, consider re-exporting it from the top level index module`, - }, - ]) - .reduce((acc, part) => [...acc, ...part], []); -} - -module.exports = { - rules: { - 'no-console': 2, - 'import/no-default-export': 'error', - '@kbn/eslint/no-restricted-paths': [ - 'error', - { - basePath: path.resolve(__dirname, '../../../../../'), - zones: topLevelRestricedZones.concat( - buildRestrictedPaths(['visualize', 'discover', 'dashboard', 'devTools']) - ), - }, - ], - }, -}; diff --git a/src/legacy/core_plugins/kibana/public/_hacks.scss b/src/legacy/core_plugins/kibana/public/_hacks.scss deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 0bf74edc77cb6..51dedcc629c76 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -38,17 +38,22 @@ import 'uiExports/shareContextMenuExtensions'; import 'uiExports/interpreter'; import 'ui/autoload/all'; -import './management'; + import { localApplicationService } from './local_application_service'; npSetup.plugins.kibanaLegacy.registerLegacyAppAlias('doc', 'discover', { keepPrefix: true }); npSetup.plugins.kibanaLegacy.registerLegacyAppAlias('context', 'discover', { keepPrefix: true }); +npSetup.plugins.kibanaLegacy.forwardApp('management', 'management', (path) => { + return path.replace('/management', ''); +}); + localApplicationService.attachToAngular(routes); routes.enable(); const { config } = npSetup.plugins.kibanaLegacy; + routes.otherwise({ redirectTo: `/${config.defaultAppId || 'discover'}`, }); diff --git a/src/legacy/core_plugins/kibana/public/management/_hacks.scss b/src/legacy/core_plugins/kibana/public/management/_hacks.scss deleted file mode 100644 index 59af9c9617a30..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/_hacks.scss +++ /dev/null @@ -1,29 +0,0 @@ -// SASSTODO: figure out why this is needed -kbn-management-app, -kbn-management-landing, -kbn-management-indices, -kbn-management-indices-edit, -kbn-management-indices-create, -kbn-management-advanced, -kbn-management-objects, -kbn-management-objects-view { - display: block; -} - -#management-landing { - display: flex; -} - -.kbn-management-tab:first-letter { - text-transform: capitalize; -} - -// SASSTODO: Remove when this is replaced with EuiCode -kbn-management-objects-view { - .ace_editor { height: 300px; } -} - -// Hack because the management wrapper is flat HTML and needs a class -.mgtPage__body { - max-width: map-get($euiBreakpoints, 'xl'); -} diff --git a/src/legacy/core_plugins/kibana/public/management/_management_app.scss b/src/legacy/core_plugins/kibana/public/management/_management_app.scss deleted file mode 100644 index bd3cabbc574d3..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/_management_app.scss +++ /dev/null @@ -1,69 +0,0 @@ -.mgtPanel { - margin-bottom: $euiSize; - background: $euiColorEmptyShade; -} - -/** - * 1. Override kuiPanelBody styles to accommodate padding of items within the panel body.. - */ -.mgtPanel__body { - padding: 5px 10px; /* 1 */ -} - -/** - * 1. Create vertical space between items when they wrap. - */ -.mgtPanel__item { - padding: 5px 15px; /* 1 */ -} - -// SASSTODO: Remove when this is replaced by the side nav -.mgtPanel__link { - @include euiFontSizeL; - - line-height: 1.5; // Make sure the space between wrapped lines is than the vertical space between items. - - &.mgtPanel__link--disabled { - opacity: $euiColorDarkShade; - cursor: default; - - &:hover, &:visited { - color: $euiColorPrimary; - } - } -} - -// SASSTODO: Remove when this form is replaced by EUI -kbn-management-objects { - form { - margin-bottom: $euiSize; - } - .list-unstyled { - li { - border-bottom: $euiBorderThin; - padding: $euiSizeS; - } - } - .empty { - color: $euiColorDarkShade; - } - - .item { - padding: $euiSizeM; - - .item-title { - margin-left: $euiSizeL; - } - - .actions { - margin-top: $euiSizeXS; - } - } - - .header { - .title, .controls { - padding-right: 1em; - display: inline-block; - } - } -} diff --git a/src/legacy/core_plugins/kibana/public/management/app.html b/src/legacy/core_plugins/kibana/public/management/app.html deleted file mode 100644 index 11198c02960c7..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/app.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
-
-
diff --git a/src/legacy/core_plugins/kibana/public/management/index.js b/src/legacy/core_plugins/kibana/public/management/index.js deleted file mode 100644 index 48f0e2517a486..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/index.js +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import uiRoutes from 'ui/routes'; -import { I18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; -import appTemplate from './app.html'; -import landingTemplate from './landing.html'; -import { management, MANAGEMENT_BREADCRUMB } from 'ui/management'; -import { ManagementSidebarNav } from '../../../../../plugins/management/public'; -import { timefilter } from 'ui/timefilter'; -import { - EuiPageContent, - EuiTitle, - EuiText, - EuiSpacer, - EuiIcon, - EuiHorizontalRule, -} from '@elastic/eui'; -import { npStart } from 'ui/new_platform'; - -const SIDENAV_ID = 'management-sidenav'; -const LANDING_ID = 'management-landing'; - -uiRoutes.when('/management', { - template: landingTemplate, - k7Breadcrumbs: () => [MANAGEMENT_BREADCRUMB], -}); - -uiRoutes.when('/management/:section', { - redirectTo: '/management', -}); - -export function updateLandingPage(version) { - const node = document.getElementById(LANDING_ID); - if (!node) { - return; - } - - render( - - -
-
- - - -

- -

-
- - - -
- - - - -

- -

-
-
-
-
, - node - ); -} - -export function updateSidebar(legacySections, id) { - const node = document.getElementById(SIDENAV_ID); - if (!node) { - return; - } - - render( - - - , - node - ); -} - -export const destroyReact = (id) => { - const node = document.getElementById(id); - node && unmountComponentAtNode(node); -}; - -uiModules.get('apps/management').directive('kbnManagementApp', function ($location) { - return { - restrict: 'E', - template: appTemplate, - transclude: true, - scope: { - sectionName: '@section', - omitPages: '@omitBreadcrumbPages', - pageTitle: '=', - }, - - link: function ($scope) { - timefilter.disableAutoRefreshSelector(); - timefilter.disableTimeRangeSelector(); - $scope.sections = management.visibleItems; - $scope.section = management.getSection($scope.sectionName) || management; - - if ($scope.section) { - $scope.section.items.forEach((item) => { - item.active = `#${$location.path()}`.indexOf(item.url) > -1; - }); - } - - updateSidebar($scope.sections, $scope.section.id); - $scope.$on('$destroy', () => destroyReact(SIDENAV_ID)); - management.addListener(() => updateSidebar(management.visibleItems, $scope.section.id)); - - updateLandingPage($scope.$root.chrome.getKibanaVersion()); - $scope.$on('$destroy', () => destroyReact(LANDING_ID)); - }, - }; -}); - -uiModules.get('apps/management').directive('kbnManagementLanding', function (kbnVersion) { - return { - restrict: 'E', - link: function ($scope) { - $scope.sections = management.visibleItems; - $scope.kbnVersion = kbnVersion; - }, - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/management/index.scss b/src/legacy/core_plugins/kibana/public/management/index.scss index 123580c0b7907..fb267b714f1c9 100644 --- a/src/legacy/core_plugins/kibana/public/management/index.scss +++ b/src/legacy/core_plugins/kibana/public/management/index.scss @@ -7,9 +7,7 @@ // mgtChart__legend--small // mgtChart__legend-isLoading -@import 'hacks'; - // Core -@import 'management_app'; @import '../../../../../plugins/advanced_settings/public/index'; + @import 'sections/index_patterns/index'; diff --git a/src/legacy/core_plugins/kibana/public/management/landing.html b/src/legacy/core_plugins/kibana/public/management/landing.html deleted file mode 100644 index 39459b26f7415..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/landing.html +++ /dev/null @@ -1,3 +0,0 @@ - -
-
diff --git a/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js b/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js index 64676b1bce75c..4df0e7a140205 100644 --- a/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js +++ b/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js @@ -69,7 +69,7 @@ const savedObjectsManagement = getManagementaMock({ }, getInAppUrl(obj) { return { - path: `/app/kibana#/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`, + path: `/app/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`, uiCapabilitiesPath: 'management.kibana.index_patterns', }; }, @@ -325,7 +325,7 @@ describe('findRelationships', () => { title: 'My Index Pattern', editUrl: '/management/kibana/indexPatterns/patterns/1', inAppUrl: { - path: '/app/kibana#/management/kibana/indexPatterns/patterns/1', + path: '/app/management/kibana/indexPatterns/patterns/1', uiCapabilitiesPath: 'management.kibana.index_patterns', }, }, @@ -439,7 +439,7 @@ describe('findRelationships', () => { title: 'My Index Pattern', editUrl: '/management/kibana/indexPatterns/patterns/1', inAppUrl: { - path: '/app/kibana#/management/kibana/indexPatterns/patterns/1', + path: '/app/management/kibana/indexPatterns/patterns/1', uiCapabilitiesPath: 'management.kibana.index_patterns', }, }, diff --git a/src/legacy/core_plugins/status_page/public/components/__snapshots__/metric_tiles.test.js.snap b/src/legacy/core_plugins/status_page/public/components/__snapshots__/metric_tiles.test.js.snap index b88210758a00d..7d4b245021c4c 100644 --- a/src/legacy/core_plugins/status_page/public/components/__snapshots__/metric_tiles.test.js.snap +++ b/src/legacy/core_plugins/status_page/public/components/__snapshots__/metric_tiles.test.js.snap @@ -4,7 +4,7 @@ exports[`byte metric 1`] = ` `; diff --git a/src/legacy/core_plugins/status_page/public/components/metric_tiles.test.js b/src/legacy/core_plugins/status_page/public/components/metric_tiles.test.js index 74dfbd4119f14..13d0a61bbc96f 100644 --- a/src/legacy/core_plugins/status_page/public/components/metric_tiles.test.js +++ b/src/legacy/core_plugins/status_page/public/components/metric_tiles.test.js @@ -47,20 +47,20 @@ const MS_METRIC = { test('general metric', () => { const component = shallow(); - expect(component).toMatchSnapshot(); // eslint-disable-line + expect(component).toMatchSnapshot(); }); test('byte metric', () => { const component = shallow(); - expect(component).toMatchSnapshot(); // eslint-disable-line + expect(component).toMatchSnapshot(); }); test('float metric', () => { const component = shallow(); - expect(component).toMatchSnapshot(); // eslint-disable-line + expect(component).toMatchSnapshot(); }); test('millisecond metric', () => { const component = shallow(); - expect(component).toMatchSnapshot(); // eslint-disable-line + expect(component).toMatchSnapshot(); }); diff --git a/src/legacy/core_plugins/status_page/public/lib/format_number.js b/src/legacy/core_plugins/status_page/public/lib/format_number.js index c5f23a9a9ef6d..4a8be4fc48a15 100644 --- a/src/legacy/core_plugins/status_page/public/lib/format_number.js +++ b/src/legacy/core_plugins/status_page/public/lib/format_number.js @@ -17,7 +17,7 @@ * under the License. */ -import numeral from 'numeral'; +import numeral from '@elastic/numeral'; export default function formatNumber(num, which) { let format = '0.00'; diff --git a/src/legacy/core_plugins/status_page/public/lib/format_number.test.js b/src/legacy/core_plugins/status_page/public/lib/format_number.test.js index 78f17ffa76f39..f70377dcba241 100644 --- a/src/legacy/core_plugins/status_page/public/lib/format_number.test.js +++ b/src/legacy/core_plugins/status_page/public/lib/format_number.test.js @@ -21,42 +21,42 @@ import formatNumber from './format_number'; describe('format byte', () => { test('zero', () => { - expect(formatNumber(0, 'byte')).toEqual('0.00 B'); + expect(formatNumber(0, 'byte')).toMatchInlineSnapshot(`"0.00 B"`); }); test('mb', () => { - expect(formatNumber(181142512, 'byte')).toEqual('181.14 MB'); + expect(formatNumber(181142512, 'byte')).toMatchInlineSnapshot(`"172.75 MB"`); }); test('gb', () => { - expect(formatNumber(273727485000, 'byte')).toEqual('273.73 GB'); + expect(formatNumber(273727485000, 'byte')).toMatchInlineSnapshot(`"254.93 GB"`); }); }); describe('format ms', () => { test('zero', () => { - expect(formatNumber(0, 'ms')).toEqual('0.00 ms'); + expect(formatNumber(0, 'ms')).toMatchInlineSnapshot(`"0.00 ms"`); }); test('sub ms', () => { - expect(formatNumber(0.128, 'ms')).toEqual('0.13 ms'); + expect(formatNumber(0.128, 'ms')).toMatchInlineSnapshot(`"0.13 ms"`); }); test('many ms', () => { - expect(formatNumber(3030.284, 'ms')).toEqual('3030.28 ms'); + expect(formatNumber(3030.284, 'ms')).toMatchInlineSnapshot(`"3030.28 ms"`); }); }); describe('format integer', () => { test('zero', () => { - expect(formatNumber(0, 'integer')).toEqual('0'); + expect(formatNumber(0, 'integer')).toMatchInlineSnapshot(`"0"`); }); test('sub integer', () => { - expect(formatNumber(0.728, 'integer')).toEqual('1'); + expect(formatNumber(0.728, 'integer')).toMatchInlineSnapshot(`"1"`); }); test('many integer', () => { - expect(formatNumber(3030.284, 'integer')).toEqual('3030'); + expect(formatNumber(3030.284, 'integer')).toMatchInlineSnapshot(`"3030"`); }); }); diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs index 1453c974c1180..e8f05b46f7061 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs @@ -73,12 +73,23 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { } load([ - {{#each jsDependencyPaths}} - '{{this}}', - {{/each}} + {{#each jsDependencyPaths}} + '{{this}}', + {{/each}} ], function () { + {{#unless legacyBundlePath}} + if (!__kbnBundles__ || !__kbnBundles__['entry/core'] || typeof __kbnBundles__['entry/core'].__kbnBootstrap__ !== 'function') { + console.error('entry/core bundle did not load correctly'); + failure(); + } else { + __kbnBundles__['entry/core'].__kbnBootstrap__() + } + {{/unless}} + load([ - '{{entryBundlePath}}', + {{#if legacyBundlePath}} + '{{legacyBundlePath}}', + {{/if}} {{#each styleSheetPaths}} '{{this}}', {{/each}} diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 10847b9928528..b09d4861b343b 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -173,6 +173,7 @@ export function uiRenderMixin(kbnServer, server, config) { `${regularBundlePath}/commons.bundle.js`, ]), + `${regularBundlePath}/core/core.entry.js`, ...kpPluginIds.map( (pluginId) => `${regularBundlePath}/plugin/${pluginId}/${pluginId}.plugin.js` ), @@ -199,9 +200,7 @@ export function uiRenderMixin(kbnServer, server, config) { jsDependencyPaths, styleSheetPaths, publicPathMap, - entryBundlePath: isCore - ? `${regularBundlePath}/core/core.entry.js` - : `${regularBundlePath}/${app.getId()}.bundle.js`, + legacyBundlePath: isCore ? undefined : `${regularBundlePath}/${app.getId()}.bundle.js`, }, }); diff --git a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx index df44ea45e9d01..b4779d051ab02 100644 --- a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx +++ b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx @@ -19,7 +19,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { HashRouter, Switch, Route } from 'react-router-dom'; +import { Router, Switch, Route } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; @@ -60,7 +60,7 @@ export async function mountManagementSection( ReactDOM.render( - + - + , params.element ); diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts index 26c1d9e5033e0..da6c940c48d0a 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts @@ -303,6 +303,47 @@ describe('createStreamingBatchedFunction()', () => { expect(await promise3).toEqual({ foo: 'bar 2' }); }); + test('resolves falsy results', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = fn({ a: '1' }); + const promise2 = fn({ b: '2' }); + const promise3 = fn({ c: '3' }); + await new Promise((r) => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 0, + result: false, + }) + '\n' + ); + stream.next( + JSON.stringify({ + id: 1, + result: 0, + }) + '\n' + ); + stream.next( + JSON.stringify({ + id: 2, + result: '', + }) + '\n' + ); + + expect(await isPending(promise1)).toBe(false); + expect(await isPending(promise2)).toBe(false); + expect(await isPending(promise3)).toBe(false); + expect(await promise1).toEqual(false); + expect(await promise2).toEqual(0); + expect(await promise3).toEqual(''); + }); + test('rejects promise on error response', async () => { const { fetchStreaming, stream } = setup(); const fn = createStreamingBatchedFunction({ diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts index f80a97137d1ab..89793fff6b325 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts @@ -106,7 +106,7 @@ export const createStreamingBatchedFunction = ( if (response.error) { responsesReceived++; items[response.id].future.reject(response.error); - } else if (response.result) { + } else if (response.result !== undefined) { responsesReceived++; items[response.id].future.resolve(response.result); } diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 08740b21f39a4..0de3982039928 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -228,7 +228,7 @@ export class DashboardPlugin const app: App = { id: DashboardConstants.DASHBOARDS_ID, title: 'Dashboard', - order: -1001, + order: 2500, euiIconType: 'dashboardApp', defaultPath: `#${DashboardConstants.LANDING_PAGE_PATH}`, updater$: this.appStateUpdater, diff --git a/src/plugins/data/common/es_query/filters/build_filter.test.ts b/src/plugins/data/common/es_query/filters/build_filter.test.ts index 22b44035d6ca8..73a4a4c788863 100644 --- a/src/plugins/data/common/es_query/filters/build_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/build_filter.test.ts @@ -18,7 +18,7 @@ */ import { buildFilter, FilterStateStore, FILTERS } from '.'; -import { stubIndexPattern, stubFields } from '../../../public/stubs'; +import { stubIndexPattern, stubFields } from '../../../common/stubs'; describe('buildFilter', () => { it('should build phrase filters', () => { diff --git a/src/plugins/data/common/es_query/filters/get_display_value.ts b/src/plugins/data/common/es_query/filters/get_display_value.ts index 03167f3080419..10b4dab3f46ef 100644 --- a/src/plugins/data/common/es_query/filters/get_display_value.ts +++ b/src/plugins/data/common/es_query/filters/get_display_value.ts @@ -25,6 +25,7 @@ import { Filter } from '../filters'; function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { if (!indexPattern || !key) return; + let format = get(indexPattern, ['fields', 'byName', key, 'format']); if (!format && (indexPattern.fields as any).getByName) { // TODO: Why is indexPatterns sometimes a map and sometimes an array? @@ -43,9 +44,8 @@ function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { } export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IIndexPattern[]): string { - const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); - if (typeof filter.meta.value === 'function') { + const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); const valueFormatter: any = getValueFormatter(indexPattern, filter.meta.key); return filter.meta.value(valueFormatter); } else { diff --git a/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.test.ts b/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.test.ts index 2f31fafcb74e4..672c0a6db4dd0 100644 --- a/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/get_index_pattern_from_filter.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { stubIndexPattern, phraseFilter } from 'src/plugins/data/public/stubs'; +import { stubIndexPattern, phraseFilter } from 'src/plugins/data/common/stubs'; import { getIndexPatternFromFilter } from './get_index_pattern_from_filter'; describe('getIndexPatternFromFilter', () => { diff --git a/src/plugins/data/common/es_query/kuery/ast/ast.ts b/src/plugins/data/common/es_query/kuery/ast/ast.ts index 01ce77fa8f578..c6f44ebc437d3 100644 --- a/src/plugins/data/common/es_query/kuery/ast/ast.ts +++ b/src/plugins/data/common/es_query/kuery/ast/ast.ts @@ -24,7 +24,7 @@ import { IIndexPattern } from '../../../index_patterns/types'; // @ts-ignore import { parse as parseKuery } from './_generated_/kuery'; -import { JsonObject } from '../../../../../kibana_utils/public'; +import { JsonObject } from '../../../../../kibana_utils/common'; const fromExpression = ( expression: string | DslQuery, diff --git a/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts b/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts index 398cb1a164415..df1fdd1e0d514 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts @@ -21,7 +21,7 @@ import _ from 'lodash'; import * as ast from '../ast'; import { nodeTypes } from '../node_types'; import { NamedArgTypeBuildNode } from './types'; -import { JsonObject } from '../../../../../kibana_utils/public'; +import { JsonObject } from '../../../../../kibana_utils/common'; export function buildNode(name: string, value: any): NamedArgTypeBuildNode { const argumentNode = diff --git a/src/plugins/data/common/es_query/kuery/node_types/types.ts b/src/plugins/data/common/es_query/kuery/node_types/types.ts index 937b5c6e7ef9c..6d3019e75d483 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/types.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/types.ts @@ -22,7 +22,7 @@ */ import { IIndexPattern } from '../../../index_patterns'; -import { JsonValue } from '../../../../../kibana_utils/public'; +import { JsonValue } from '../../../../../kibana_utils/common'; import { KueryNode } from '..'; export type FunctionName = diff --git a/src/plugins/data/public/index_patterns/field.stub.ts b/src/plugins/data/common/index_patterns/field.stub.ts similarity index 96% rename from src/plugins/data/public/index_patterns/field.stub.ts rename to src/plugins/data/common/index_patterns/field.stub.ts index 2e94f4b45f400..cbb3d2fa2ce68 100644 --- a/src/plugins/data/public/index_patterns/field.stub.ts +++ b/src/plugins/data/common/index_patterns/field.stub.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IFieldType } from '../../../../plugins/data/public'; +import { IFieldType } from '.'; export const stubFields: IFieldType[] = [ { diff --git a/src/plugins/data/public/index_patterns/index_pattern.stub.ts b/src/plugins/data/common/index_patterns/index_pattern.stub.ts similarity index 96% rename from src/plugins/data/public/index_patterns/index_pattern.stub.ts rename to src/plugins/data/common/index_patterns/index_pattern.stub.ts index 4f8108575aa15..e7384e09494aa 100644 --- a/src/plugins/data/public/index_patterns/index_pattern.stub.ts +++ b/src/plugins/data/common/index_patterns/index_pattern.stub.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IIndexPattern } from '../../common'; +import { IIndexPattern } from '.'; import { stubFields } from './field.stub'; export const stubIndexPattern: IIndexPattern = { diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts index 0c3947ade8221..1e5391332e6b0 100644 --- a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts +++ b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts @@ -228,5 +228,21 @@ describe('filter manager utilities', () => { expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeFalsy(); }); + + test('should compare index with index true', () => { + const f1 = { + $state: { store: FilterStateStore.GLOBAL_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''), + }; + const f2 = { + $state: { store: FilterStateStore.GLOBAL_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''), + }; + + f2.meta.index = 'wassup'; + f2.meta.index = 'dog'; + + expect(compareFilters([f1], [f2], { index: true })).toBeFalsy(); + }); }); }); diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.ts b/src/plugins/data/common/query/filter_manager/compare_filters.ts index 3be52a9a60977..65df6e26a25b3 100644 --- a/src/plugins/data/common/query/filter_manager/compare_filters.ts +++ b/src/plugins/data/common/query/filter_manager/compare_filters.ts @@ -21,6 +21,7 @@ import { defaults, isEqual, omit, map } from 'lodash'; import { FilterMeta, Filter } from '../../es_query'; export interface FilterCompareOptions { + index?: boolean; disabled?: boolean; negate?: boolean; state?: boolean; @@ -31,6 +32,7 @@ export interface FilterCompareOptions { * Include disabled, negate and store when comparing filters */ export const COMPARE_ALL_OPTIONS: FilterCompareOptions = { + index: true, disabled: true, negate: true, state: true, @@ -44,6 +46,7 @@ const mapFilter = ( ) => { const cleaned: FilterMeta = omit(filter, excludedAttributes); + if (comparators.index) cleaned.index = filter.meta?.index; if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate); if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled); if (comparators.alias) cleaned.alias = filter.meta?.alias; @@ -81,6 +84,7 @@ export const compareFilters = ( const excludedAttributes: string[] = ['$$hashKey', 'meta']; comparators = defaults(comparatorOptions || {}, { + index: false, state: false, negate: false, disabled: false, diff --git a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts b/src/plugins/data/common/stubs.ts similarity index 80% rename from src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts rename to src/plugins/data/common/stubs.ts index 587a372f91555..aea2b71eec46b 100644 --- a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts +++ b/src/plugins/data/common/stubs.ts @@ -17,8 +17,6 @@ * under the License. */ -import { npSetup } from 'ui/new_platform'; - -const registry = npSetup.plugins.savedObjectsManagement?.serviceRegistry; - -export const savedObjectManagementRegistry = registry!; +export { stubIndexPattern, stubIndexPatternWithFields } from './index_patterns/index_pattern.stub'; +export { stubFields } from './index_patterns/field.stub'; +export * from './es_query/filters/stubs'; diff --git a/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx b/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx index 9115e523f5302..2088bd8c925df 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx +++ b/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx @@ -91,9 +91,9 @@ export const createEnsureDefaultIndexPattern = (core: CoreStart) => { if (redirectTarget === '/home') { core.application.navigateToApp('home'); } else { - window.location.href = core.http.basePath.prepend( - `/app/kibana#/management/kibana/indexPatterns?bannerMessage=${bannerMessage}` - ); + core.application.navigateToApp('management', { + path: `/kibana/indexPatterns?bannerMessage=${bannerMessage}`, + }); } // return never-resolving promise to stop resolving and wait for the url change diff --git a/src/plugins/data/public/search/long_query_notification.tsx b/src/plugins/data/public/search/long_query_notification.tsx index 0bdf8ab7c66f8..1db298618fae8 100644 --- a/src/plugins/data/public/search/long_query_notification.tsx +++ b/src/plugins/data/public/search/long_query_notification.tsx @@ -44,7 +44,7 @@ export function LongQueryNotification(props: Props) { { - await props.application.navigateToApp('kibana#/management/stack/license_management'); + await props.application.navigateToApp('management/stack/license_management'); }} > onUpdate(i, newFilter)} onRemove={() => onRemove(i)} diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx index fd228a2213795..0e2bcc7581950 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx @@ -198,9 +198,14 @@ class FilterEditorUI extends Component { if ( this.props.indexPatterns.length <= 1 && this.props.indexPatterns.find( - (indexPattern) => indexPattern === this.state.selectedIndexPattern + (indexPattern) => indexPattern === this.getIndexPatternFromFilter() ) ) { + /** + * Don't render the index pattern selector if there's just one \ zero index patterns + * and if the index pattern the filter was LOADED with is in the indexPatterns list. + **/ + return ''; } const { selectedIndexPattern } = this.state; diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 528ec4800e7b9..c44e1faeb8e7f 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -18,9 +18,9 @@ */ import { EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { InjectedIntl } from '@kbn/i18n/react'; import classNames from 'classnames'; -import React, { Component, MouseEvent } from 'react'; +import React, { MouseEvent, useState, useEffect } from 'react'; import { IUiSettingsClient } from 'src/core/public'; import { FilterEditor } from './filter_editor'; import { FilterView } from './filter_view'; @@ -32,8 +32,9 @@ import { toggleFilterNegated, toggleFilterPinned, toggleFilterDisabled, + getIndexPatternFromFilter, } from '../../../common'; -import { getNotifications } from '../../services'; +import { getIndexPatterns } from '../../services'; interface Props { id: string; @@ -46,95 +47,123 @@ interface Props { uiSettings: IUiSettingsClient; } -interface State { - isPopoverOpen: boolean; +interface LabelOptions { + title: string; + status: string; + message?: string; } -class FilterItemUI extends Component { - public state = { - isPopoverOpen: false, - }; +const FILTER_ITEM_OK = ''; +const FILTER_ITEM_WARNING = 'warn'; +const FILTER_ITEM_ERROR = 'error'; - private handleBadgeClick = (e: MouseEvent) => { - if (e.shiftKey) { - this.onToggleDisabled(); +export function FilterItem(props: Props) { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [indexPatternExists, setIndexPatternExists] = useState(undefined); + const { id, filter, indexPatterns } = props; + + useEffect(() => { + const index = props.filter.meta.index; + if (index) { + getIndexPatterns() + .get(index) + .then((indexPattern) => { + setIndexPatternExists(!!indexPattern); + }) + .catch(() => { + setIndexPatternExists(false); + }); } else { - this.togglePopover(); + setIndexPatternExists(false); } - }; - public render() { - const { filter, id } = this.props; - const { negate, disabled } = filter.meta; - let hasError: boolean = false; + }, [props.filter.meta.index]); - let valueLabel; - try { - valueLabel = getDisplayValueFromFilter(filter, this.props.indexPatterns); - } catch (e) { - getNotifications().toasts.addError(e, { - title: this.props.intl.formatMessage({ - id: 'data.filter.filterBar.labelErrorMessage', - defaultMessage: 'Failed to display filter', - }), - }); - valueLabel = this.props.intl.formatMessage({ - id: 'data.filter.filterBar.labelErrorText', - defaultMessage: 'Error', - }); - hasError = true; + function handleBadgeClick(e: MouseEvent) { + if (e.shiftKey) { + onToggleDisabled(); + } else { + setIsPopoverOpen(!isPopoverOpen); } - const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; - const dataTestSubjValue = filter.meta.value ? `filter-value-${valueLabel}` : ''; - const dataTestSubjDisabled = `filter-${ - this.props.filter.meta.disabled ? 'disabled' : 'enabled' - }`; - const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; + } - const classes = classNames( + function onSubmit(f: Filter) { + setIsPopoverOpen(false); + props.onUpdate(f); + } + + function onTogglePinned() { + const f = toggleFilterPinned(filter); + props.onUpdate(f); + } + + function onToggleNegated() { + const f = toggleFilterNegated(filter); + props.onUpdate(f); + } + + function onToggleDisabled() { + const f = toggleFilterDisabled(filter); + props.onUpdate(f); + } + + function isValidLabel(labelConfig: LabelOptions) { + return labelConfig.status === FILTER_ITEM_OK; + } + + function isDisabled(labelConfig: LabelOptions) { + const { disabled } = filter.meta; + return disabled || labelConfig.status === FILTER_ITEM_ERROR; + } + + function getClasses(negate: boolean, labelConfig: LabelOptions) { + return classNames( 'globalFilterItem', { - 'globalFilterItem-isDisabled': disabled || hasError, - 'globalFilterItem-isInvalid': hasError, + 'globalFilterItem-isDisabled': isDisabled(labelConfig), + 'globalFilterItem-isError': labelConfig.status === FILTER_ITEM_ERROR, + 'globalFilterItem-isWarning': labelConfig.status === FILTER_ITEM_WARNING, 'globalFilterItem-isPinned': isFilterPinned(filter), 'globalFilterItem-isExcluded': negate, }, - this.props.className + props.className ); + } - const badge = ( - this.props.onRemove()} - onClick={this.handleBadgeClick} - data-test-subj={`filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned}`} - /> - ); + function getDataTestSubj(labelConfig: LabelOptions) { + const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; + const dataTestSubjValue = filter.meta.value + ? `filter-value-${isValidLabel(labelConfig) ? labelConfig.title : labelConfig.status}` + : ''; + const dataTestSubjDisabled = `filter-${isDisabled(labelConfig) ? 'disabled' : 'enabled'}`; + const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; + return `filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned}`; + } - const panelTree = [ + function getPanels() { + const { negate, disabled } = filter.meta; + return [ { id: 0, items: [ { name: isFilterPinned(filter) - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.unpinFilterButtonLabel', defaultMessage: 'Unpin', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.pinFilterButtonLabel', defaultMessage: 'Pin across all apps', }), icon: 'pin', onClick: () => { - this.closePopover(); - this.onTogglePinned(); + setIsPopoverOpen(false); + onTogglePinned(); }, 'data-test-subj': 'pinFilter', }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.filterBar.editFilterButtonLabel', defaultMessage: 'Edit filter', }), @@ -144,47 +173,47 @@ class FilterItemUI extends Component { }, { name: negate - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.includeFilterButtonLabel', defaultMessage: 'Include results', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.excludeFilterButtonLabel', defaultMessage: 'Exclude results', }), icon: negate ? 'plusInCircle' : 'minusInCircle', onClick: () => { - this.closePopover(); - this.onToggleNegated(); + setIsPopoverOpen(false); + onToggleNegated(); }, 'data-test-subj': 'negateFilter', }, { name: disabled - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.enableFilterButtonLabel', defaultMessage: 'Re-enable', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.disableFilterButtonLabel', defaultMessage: 'Temporarily disable', }), icon: `${disabled ? 'eye' : 'eyeClosed'}`, onClick: () => { - this.closePopover(); - this.onToggleDisabled(); + setIsPopoverOpen(false); + onToggleDisabled(); }, 'data-test-subj': 'disableFilter', }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.filterBar.deleteFilterButtonLabel', defaultMessage: 'Delete', }), icon: 'trash', onClick: () => { - this.closePopover(); - this.props.onRemove(); + setIsPopoverOpen(false); + props.onRemove(); }, 'data-test-subj': 'deleteFilter', }, @@ -197,63 +226,124 @@ class FilterItemUI extends Component {
{ + setIsPopoverOpen(false); + }} />
), }, ]; - - return ( - - - - ); } - private closePopover = () => { - this.setState({ - isPopoverOpen: false, - }); - }; + /** + * Checks if filter field exists in any of the index patterns provided, + * Because if so, a filter for the wrong index pattern may still be applied. + * This function makes this behavior explicit, but it needs to be revised. + */ + function isFilterApplicable() { + const ip = getIndexPatternFromFilter(filter, indexPatterns); + if (ip) return true; - private togglePopover = () => { - this.setState({ - isPopoverOpen: !this.state.isPopoverOpen, + const allFields = indexPatterns.map((indexPattern) => { + return indexPattern.fields.map((field) => field.name); }); - }; + const flatFields = allFields.reduce((acc: string[], it: string[]) => [...acc, ...it], []); + return flatFields.includes(filter.meta?.key || ''); + } - private onSubmit = (filter: Filter) => { - this.closePopover(); - this.props.onUpdate(filter); - }; + function getValueLabel(): LabelOptions { + const label = { + title: '', + message: '', + status: FILTER_ITEM_OK, + }; + if (indexPatternExists === false) { + label.status = FILTER_ITEM_ERROR; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelErrorText', + defaultMessage: `Error`, + }); + label.message = props.intl.formatMessage( + { + id: 'data.filter.filterBar.labelErrorInfo', + defaultMessage: 'Index pattern {indexPattern} not found', + }, + { + indexPattern: filter.meta.index, + } + ); + } else if (isFilterApplicable()) { + try { + label.title = getDisplayValueFromFilter(filter, indexPatterns); + } catch (e) { + label.status = FILTER_ITEM_ERROR; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelErrorText', + defaultMessage: `Error`, + }); + label.message = e.message; + } + } else { + label.status = FILTER_ITEM_WARNING; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelWarningText', + defaultMessage: `Warning`, + }); + label.message = props.intl.formatMessage( + { + id: 'data.filter.filterBar.labelWarningInfo', + defaultMessage: 'Field {fieldName} does not exist in current view', + }, + { + fieldName: filter.meta.key, + } + ); + } - private onTogglePinned = () => { - const filter = toggleFilterPinned(this.props.filter); - this.props.onUpdate(filter); - }; + return label; + } - private onToggleNegated = () => { - const filter = toggleFilterNegated(this.props.filter); - this.props.onUpdate(filter); - }; + // Don't render until we know if the index pattern is valid + if (indexPatternExists === undefined) return null; + const valueLabelConfig = getValueLabel(); - private onToggleDisabled = () => { - const filter = toggleFilterDisabled(this.props.filter); - this.props.onUpdate(filter); - }; -} + // Disable errored filters and re-render + if (valueLabelConfig.status === FILTER_ITEM_ERROR && !filter.meta.disabled) { + filter.meta.disabled = true; + props.onUpdate(filter); + return null; + } -export const FilterItem = injectI18n(FilterItemUI); + const badge = ( + props.onRemove()} + onClick={handleBadgeClick} + data-test-subj={getDataTestSubj(valueLabelConfig)} + /> + ); + + return ( + { + setIsPopoverOpen(false); + }} + button={badge} + anchorPosition="downLeft" + withTitle={true} + panelPaddingSize="none" + > + + + ); +} diff --git a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx index 6ff261e3cfb8a..f9328875cc910 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx @@ -26,6 +26,7 @@ import { Filter, isFilterPinned } from '../../../../common'; interface Props { filter: Filter; valueLabel: string; + errorMessage?: string; [propName: string]: any; } @@ -34,14 +35,17 @@ export const FilterView: FC = ({ iconOnClick, onClick, valueLabel, + errorMessage, ...rest }: Props) => { const [ref, innerText] = useInnerText(); - let title = i18n.translate('data.filter.filterBar.moreFilterActionsMessage', { - defaultMessage: 'Filter: {innerText}. Select for more filter actions.', - values: { innerText }, - }); + let title = + errorMessage || + i18n.translate('data.filter.filterBar.moreFilterActionsMessage', { + defaultMessage: 'Filter: {innerText}. Select for more filter actions.', + values: { innerText }, + }); if (isFilterPinned(filter)) { title = `${i18n.translate('data.filter.filterBar.pinnedFilterPrefix', { diff --git a/src/plugins/data/server/saved_objects/index_patterns.ts b/src/plugins/data/server/saved_objects/index_patterns.ts index a212d7f88e4eb..ee02f38427914 100644 --- a/src/plugins/data/server/saved_objects/index_patterns.ts +++ b/src/plugins/data/server/saved_objects/index_patterns.ts @@ -36,7 +36,7 @@ export const indexPatternSavedObjectType: SavedObjectsType = { }, getInAppUrl(obj) { return { - path: `/app/kibana#/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`, + path: `/app/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`, uiCapabilitiesPath: 'management.kibana.index_patterns', }; }, diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index daea8b5938042..1a2bff8211db5 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -162,8 +162,8 @@ app.config(($routeProvider) => { mapping: { search: '/', 'index-pattern': { - app: 'kibana', - path: `#/management/kibana/objects/savedSearches/${$route.current.params.id}`, + app: 'management', + path: `kibana/objects/savedSearches/${$route.current.params.id}`, }, }, toastNotifications, @@ -870,6 +870,7 @@ function discoverController( if ($scope.vis.data.aggs.aggs[1]) { $scope.bucketInterval = $scope.vis.data.aggs.aggs[1].buckets.getInterval(); } + $scope.updateTime(); } $scope.hits = resp.hits.total; diff --git a/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap b/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap index 3204252c808ac..42cd8613b1de0 100644 --- a/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap +++ b/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap @@ -53,7 +53,7 @@ exports[`render 1`] = ` > { return { getServices: () => ({ core: { uiSettings: {}, savedObjects: {} }, + addBasePath: (path) => path, }), }; }); diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 5a031872913c0..4323e3d8deda4 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -188,7 +188,7 @@ export class DiscoverPlugin id: 'discover', title: 'Discover', updater$: this.appStateUpdater.asObservable(), - order: -1004, + order: 1000, euiIconType: 'discoverApp', defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, diff --git a/src/plugins/expressions/common/ast/types.ts b/src/plugins/expressions/common/ast/types.ts index 0b505f117a580..d5039d0adb318 100644 --- a/src/plugins/expressions/common/ast/types.ts +++ b/src/plugins/expressions/common/ast/types.ts @@ -18,7 +18,7 @@ */ import { ExpressionValue, ExpressionValueError } from '../expression_types'; -import { ExpressionFunction } from '../../public'; +import { ExpressionFunction } from '../../common'; export type ExpressionAstNode = | ExpressionAstExpression diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index 6a2f4bb269ff3..2e83d16dd778e 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -20,7 +20,7 @@ import { Execution } from './execution'; import { parseExpression, ExpressionAstExpression } from '../ast'; import { createUnitTestExecutor } from '../test_helpers'; -import { ExpressionFunctionDefinition } from '../../public'; +import { ExpressionFunctionDefinition } from '../../common'; import { ExecutionContract } from './execution_contract'; beforeAll(() => { diff --git a/src/plugins/expressions/common/util/create_error.ts b/src/plugins/expressions/common/util/create_error.ts index bc27b0eda4959..876e7dfec799c 100644 --- a/src/plugins/expressions/common/util/create_error.ts +++ b/src/plugins/expressions/common/util/create_error.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ExpressionValueError } from '../../public'; +import { ExpressionValueError } from '../../common'; type ErrorLike = Partial>; diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index 702f88d785756..7c1711f056d69 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -88,6 +88,31 @@ describe('ExpressionRenderer', () => { expect(instance.find(EuiProgress)).toHaveLength(0); }); + it('updates the expression loader when refresh subject emits', () => { + const refreshSubject = new Subject(); + const loaderUpdate = jest.fn(); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$: new Subject(), + data$: new Subject(), + loading$: new Subject(), + update: loaderUpdate, + destroy: jest.fn(), + }; + }); + + const instance = mount(); + + act(() => { + refreshSubject.next(); + }); + + expect(loaderUpdate).toHaveBeenCalled(); + + instance.unmount(); + }); + it('should display a custom error message if the user provides one and then remove it after successful render', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index a83c63443906b..bf716a3b9b1e8 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -19,7 +19,7 @@ import React, { useRef, useEffect, useState, useLayoutEffect } from 'react'; import classNames from 'classnames'; -import { Subscription } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect'; import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; @@ -38,6 +38,10 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { renderError?: (error?: string | null) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; onEvent?: (event: ExpressionRendererEvent) => void; + /** + * An observable which can be used to re-run the expression without destroying the component + */ + reload$?: Observable; } export type ReactExpressionRendererType = React.ComponentType; @@ -63,6 +67,7 @@ export const ReactExpressionRenderer = ({ renderError, expression, onEvent, + reload$, ...expressionLoaderOptions }: ReactExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); @@ -135,6 +140,15 @@ export const ReactExpressionRenderer = ({ }; }, [hasCustomRenderErrorHandler, onEvent]); + useEffect(() => { + const subscription = reload$?.subscribe(() => { + if (expressionLoaderRef.current) { + expressionLoaderRef.current.update(expression, expressionLoaderOptions); + } + }); + return () => subscription?.unsubscribe(); + }, [reload$, expression, ...Object.values(expressionLoaderOptions)]); + // Re-fetch data automatically when the inputs change useShallowCompareEffect( () => { diff --git a/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap index 924de9bbd0994..3b3f86e579f1a 100644 --- a/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap @@ -277,7 +277,7 @@ exports[`apmUiEnabled 1`] = ` /> { const basePath = getServices().getBasePath(); + const renderCards = () => { const apmData = { title: intl.formatMessage({ @@ -296,7 +297,7 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => { { id="home.dataManagementDisableCollection" defaultMessage=" To stop collection, " /> - + { id="home.dataManagementEnableCollection" defaultMessage=" To start collection, " /> - + { - history.push(`${url}?_a=(tab:${field?.scripted ? TAB_SCRIPTED_FIELDS : TAB_INDEXED_FIELDS})`); + history.push( + `${url}#/?_a=(tab:${field?.scripted ? TAB_SCRIPTED_FIELDS : TAB_INDEXED_FIELDS})` + ); }; if (field) { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx index 11fdae39aee3c..a0d6a43d6f776 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx @@ -20,6 +20,8 @@ import React from 'react'; import { render } from 'enzyme'; import { RouteComponentProps } from 'react-router-dom'; +import { ScopedHistory } from 'kibana/public'; +import { scopedHistoryMock } from '../../../../../../../../core/public/mocks'; import { Header } from './header'; @@ -28,7 +30,7 @@ describe('Header', () => { const component = render( diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx index dc48f61d1aa65..e432b9b466367 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx @@ -22,9 +22,13 @@ import { withRouter, RouteComponentProps } from 'react-router-dom'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ScopedHistory } from 'kibana/public'; + +import { reactRouterNavigate } from '../../../../../../../kibana_react/public'; interface HeaderProps extends RouteComponentProps { indexPatternId: string; + history: ScopedHistory; } export const Header = withRouter(({ indexPatternId, history }: HeaderProps) => ( @@ -52,9 +56,7 @@ export const Header = withRouter(({ indexPatternId, history }: HeaderProps) => ( { - history.push(`${indexPatternId}/create-field/`); - }} + {...reactRouterNavigate(history, `patterns/${indexPatternId}/create-field/`)} > ; - } - ) => ( - - {name} - {index.tags && - index.tags.map(({ key: tagKey, name: tagName }) => ( - - {tagName} - - ))} - - ), - dataType: 'string' as const, - sortable: ({ sort }: { sort: string }) => sort, - }, -]; - const pagination = { initialPageSize: 10, pageSizeOptions: [5, 10, 25, 50], @@ -140,6 +112,39 @@ export const IndexPatternTable = ({ canSave, history }: Props) => { chrome.docTitle.change(title); + const columns = [ + { + field: 'title', + name: 'Pattern', + render: ( + name: string, + index: { + id: string; + tags?: Array<{ + key: string; + name: string; + }>; + } + ) => ( + <> + + {name} + + + {index.tags && + index.tags.map(({ key: tagKey, name: tagName }) => ( + + {tagName} + + ))} + + + ), + dataType: 'string' as const, + sortable: ({ sort }: { sort: string }) => sort, + }, + ]; + const createButton = canSave ? ( - + @@ -93,7 +93,7 @@ export async function mountManagementSection( - + , params.element diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts index ebcd92f25c13b..a98cc05a0a80a 100644 --- a/src/plugins/index_pattern_management/public/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -71,7 +71,7 @@ export class IndexPatternManagementPlugin throw new Error('`kibana` management section not found.'); } - const newAppPath = `kibana#/management/kibana/${IPM_APP_ID}`; + const newAppPath = `management/kibana/${IPM_APP_ID}`; const legacyPatternsPath = 'management/kibana/index_patterns'; kibanaLegacy.forwardApp('management/kibana/index_pattern', newAppPath, (path) => '/create'); diff --git a/src/plugins/kibana_legacy/public/angular/angular_config.tsx b/src/plugins/kibana_legacy/public/angular/angular_config.tsx index ed91d1726f1ac..fcfe2bc7f86a2 100644 --- a/src/plugins/kibana_legacy/public/angular/angular_config.tsx +++ b/src/plugins/kibana_legacy/public/angular/angular_config.tsx @@ -426,7 +426,11 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart, isLocalAngular: boole values={{ storeInSessionStorageParam: state:storeInSessionStorage, advancedSettingsLink: ( - + = { deprecations: ({ renameFromRoot }) => [ // TODO: Remove deprecation once defaultAppId is deleted renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', true), + (completeConfig: Record, rootPath: string, log: ConfigDeprecationLogger) => { + if ( + get(completeConfig, 'kibana.defaultAppId') === undefined && + get(completeConfig, 'kibana_legacy.defaultAppId') === undefined + ) { + return completeConfig; + } + log( + `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the \`defaultRoute\` advanced setting instead` + ); + return completeConfig; + }, ], }; diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 9bec91b859ab7..c7fd734a56cec 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -25,6 +25,7 @@ export * from './ui_settings'; export * from './field_icon'; export * from './table_list_view'; export * from './split_panel'; +export * from './react_router_navigate'; export { ValidatedDualRange, Value } from './validated_range'; export * from './notifications'; export { Markdown, MarkdownSimple } from './markdown'; diff --git a/src/plugins/management/public/legacy/index.js b/src/plugins/kibana_react/public/react_router_navigate/index.ts similarity index 87% rename from src/plugins/management/public/legacy/index.js rename to src/plugins/kibana_react/public/react_router_navigate/index.ts index f2e0ba89b7b59..b00cc30ab031f 100644 --- a/src/plugins/management/public/legacy/index.js +++ b/src/plugins/kibana_react/public/react_router_navigate/index.ts @@ -17,5 +17,4 @@ * under the License. */ -export { LegacyManagementAdapter } from './sections_register'; -export { LegacyManagementSection } from './section'; +export { reactRouterNavigate, reactRouterOnClickHandler } from './react_router_navigate'; diff --git a/src/plugins/kibana_react/public/react_router_navigate/react_router_navigate.tsx b/src/plugins/kibana_react/public/react_router_navigate/react_router_navigate.tsx new file mode 100644 index 0000000000000..7a9fe19273324 --- /dev/null +++ b/src/plugins/kibana_react/public/react_router_navigate/react_router_navigate.tsx @@ -0,0 +1,70 @@ +/* + * 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 { ScopedHistory } from 'kibana/public'; +import { History } from 'history'; + +interface LocationObject { + pathname?: string; + search?: string; + hash?: string; +} + +const isModifiedEvent = (event: any) => + !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); + +const isLeftClickEvent = (event: any) => event.button === 0; + +export const toLocationObject = (to: string | LocationObject) => + typeof to === 'string' ? { pathname: to } : to; + +export const reactRouterNavigate = ( + history: ScopedHistory | History, + to: string | LocationObject, + onClickCallback?: Function +) => ({ + href: history.createHref(toLocationObject(to)), + onClick: reactRouterOnClickHandler(history, toLocationObject(to), onClickCallback), +}); + +export const reactRouterOnClickHandler = ( + history: ScopedHistory | History, + to: string | LocationObject, + onClickCallback?: Function +) => (event: any) => { + if (onClickCallback) { + onClickCallback(event); + } + + if (event.defaultPrevented) { + return; + } + + if (event.target.getAttribute('target')) { + return; + } + + if (isModifiedEvent(event) || !isLeftClickEvent(event)) { + return; + } + + // prevents page reload + event.preventDefault(); + history.push(toLocationObject(to)); +}; diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts index 8db1d60f09d72..a8c3aab2202d1 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts @@ -35,7 +35,7 @@ import { ScopedHistory } from '../../../../../core/public'; describe('kbn_url_storage', () => { describe('getStateFromUrl & setStateToUrl', () => { - const url = 'http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id'; + const url = 'http://localhost:5601/oxf/app/kibana#/yourApp'; const state1 = { testStr: '123', testNumber: 0, @@ -50,14 +50,14 @@ describe('kbn_url_storage', () => { it('should set expanded state to url', () => { let newUrl = setStateToKbnUrl('_s', state1, { useHash: false }, url); expect(newUrl).toMatchInlineSnapshot( - `"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_s=(testArray:!(1,2,()),testNull:!n,testNumber:0,testObj:(test:'123'),testStr:'123')"` + `"http://localhost:5601/oxf/app/kibana#/yourApp?_s=(testArray:!(1,2,()),testNull:!n,testNumber:0,testObj:(test:'123'),testStr:'123')"` ); const retrievedState1 = getStateFromKbnUrl('_s', newUrl); expect(retrievedState1).toEqual(state1); newUrl = setStateToKbnUrl('_s', state2, { useHash: false }, newUrl); expect(newUrl).toMatchInlineSnapshot( - `"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_s=(test:'123')"` + `"http://localhost:5601/oxf/app/kibana#/yourApp?_s=(test:'123')"` ); const retrievedState2 = getStateFromKbnUrl('_s', newUrl); expect(retrievedState2).toEqual(state2); @@ -66,14 +66,14 @@ describe('kbn_url_storage', () => { it('should set hashed state to url', () => { let newUrl = setStateToKbnUrl('_s', state1, { useHash: true }, url); expect(newUrl).toMatchInlineSnapshot( - `"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_s=h@a897fac"` + `"http://localhost:5601/oxf/app/kibana#/yourApp?_s=h@a897fac"` ); const retrievedState1 = getStateFromKbnUrl('_s', newUrl); expect(retrievedState1).toEqual(state1); newUrl = setStateToKbnUrl('_s', state2, { useHash: true }, newUrl); expect(newUrl).toMatchInlineSnapshot( - `"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_s=h@40f94d5"` + `"http://localhost:5601/oxf/app/kibana#/yourApp?_s=h@40f94d5"` ); const retrievedState2 = getStateFromKbnUrl('_s', newUrl); expect(retrievedState2).toEqual(state2); @@ -244,67 +244,55 @@ describe('kbn_url_storage', () => { it('should extract path relative to browser history without basename', () => { const history = createBrowserHistory(); const url = - "http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + "http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; const relativePath = getRelativeToHistoryPath(url, history); expect(relativePath).toEqual( - "/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" + "/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" ); }); it('should extract path relative to browser history with basename', () => { const url = - "http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + "http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; const history1 = createBrowserHistory({ basename: '/oxf/app/' }); const relativePath1 = getRelativeToHistoryPath(url, history1); expect(relativePath1).toEqual( - "/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" + "/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" ); const history2 = createBrowserHistory({ basename: '/oxf/app/kibana/' }); const relativePath2 = getRelativeToHistoryPath(url, history2); - expect(relativePath2).toEqual( - "#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" - ); + expect(relativePath2).toEqual("#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"); }); it('should extract path relative to browser history with basename from relative url', () => { const history = createBrowserHistory({ basename: '/oxf/app/' }); - const url = - "/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + const url = "/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; const relativePath = getRelativeToHistoryPath(url, history); - expect(relativePath).toEqual( - "/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" - ); + expect(relativePath).toEqual("/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"); }); it('should extract path relative to hash history without basename', () => { const history = createHashHistory(); const url = - "http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + "http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; const relativePath = getRelativeToHistoryPath(url, history); - expect(relativePath).toEqual( - "/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" - ); + expect(relativePath).toEqual("/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"); }); it('should extract path relative to hash history with basename', () => { const history = createHashHistory({ basename: 'management' }); const url = - "http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + "http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; const relativePath = getRelativeToHistoryPath(url, history); - expect(relativePath).toEqual( - "/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" - ); + expect(relativePath).toEqual("/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"); }); it('should extract path relative to hash history with basename from relative url', () => { const history = createHashHistory({ basename: 'management' }); - const url = - "/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + const url = "/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; const relativePath = getRelativeToHistoryPath(url, history); - expect(relativePath).toEqual( - "/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" - ); + expect(relativePath).toEqual("/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"); }); }); }); diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts index 20816c08c550e..d9149095a2fa2 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts @@ -31,7 +31,7 @@ import { url as urlUtils } from '../../../common'; * e.g.: * * given an url: - * http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'') + * http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'') * will return object: * {_a: {tab: 'indexedFields'}, _b: {f: 'test', i: '', l: ''}}; */ @@ -57,7 +57,7 @@ export function getStatesFromKbnUrl( * e.g.: * * given an url: - * http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'') + * http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'') * and key '_a' * will return object: * {tab: 'indexedFields'} @@ -74,12 +74,12 @@ export function getStateFromKbnUrl( * Doesn't actually updates history * * e.g.: - * given a url: http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'') + * given a url: http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'') * key: '_a' * and state: {tab: 'other'} * * will return url: - * http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:other)&_b=(f:test,i:'',l:'') + * http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:other)&_b=(f:test,i:'',l:'') */ export function setStateToKbnUrl( key: string, diff --git a/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap b/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap deleted file mode 100644 index 7f13472ee02ee..0000000000000 --- a/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Management app can mount and unmount 1`] = ` -
-
- Test App - Hello world! -
-
-`; - -exports[`Management app can mount and unmount 2`] = `
`; diff --git a/src/legacy/ui/public/management/index.d.ts b/src/plugins/management/public/application.tsx similarity index 56% rename from src/legacy/ui/public/management/index.d.ts rename to src/plugins/management/public/application.tsx index 529efd36623a3..5d014504b8938 100644 --- a/src/legacy/ui/public/management/index.d.ts +++ b/src/plugins/management/public/application.tsx @@ -17,11 +17,26 @@ * under the License. */ -declare module 'ui/management' { - export const SidebarNav: React.FC; - export const management: any; // TODO - properly provide types - export const MANAGEMENT_BREADCRUMB: { - text: string; - href: string; - }; -} +import React from 'react'; +import ReactDOM from 'react-dom'; + +import { AppMountContext, AppMountParameters } from 'kibana/public'; +import { ManagementApp, ManagementAppDependencies } from './components/management_app'; + +export const renderApp = async ( + context: AppMountContext, + { history, appBasePath, element }: AppMountParameters, + dependencies: ManagementAppDependencies +) => { + ReactDOM.render( + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/src/plugins/management/public/components/_index.scss b/src/plugins/management/public/components/_index.scss deleted file mode 100644 index df0ebb48803d9..0000000000000 --- a/src/plugins/management/public/components/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './management_sidebar_nav/index'; diff --git a/src/plugins/management/public/components/index.ts b/src/plugins/management/public/components/index.ts index 2650d23d3c25c..8979809c5245e 100644 --- a/src/plugins/management/public/components/index.ts +++ b/src/plugins/management/public/components/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export { ManagementSidebarNav } from './management_sidebar_nav'; -export { ManagementChrome } from './management_chrome'; +export { ManagementApp } from './management_app'; +export { managementSections } from './management_sections'; diff --git a/webpackShims/numeral.js b/src/plugins/management/public/components/landing/index.ts similarity index 94% rename from webpackShims/numeral.js rename to src/plugins/management/public/components/landing/index.ts index d9551e05aa6d6..79a1c2b1145c2 100644 --- a/webpackShims/numeral.js +++ b/src/plugins/management/public/components/landing/index.ts @@ -17,4 +17,4 @@ * under the License. */ -module.exports = require('@elastic/numeral'); +export { ManagementLandingPage } from './landing'; diff --git a/src/plugins/management/public/components/landing/landing.tsx b/src/plugins/management/public/components/landing/landing.tsx new file mode 100644 index 0000000000000..f15374173e5f3 --- /dev/null +++ b/src/plugins/management/public/components/landing/landing.tsx @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiHorizontalRule, + EuiIcon, + EuiPageContent, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +interface ManagementLandingPageProps { + version: string; + setBreadcrumbs: () => void; +} + +export const ManagementLandingPage = ({ version, setBreadcrumbs }: ManagementLandingPageProps) => { + setBreadcrumbs(); + + return ( + +
+
+ + + +

+ +

+
+ + + +
+ + + + +

+ +

+
+
+
+ ); +}; diff --git a/src/plugins/management/public/components/management_app/index.ts b/src/plugins/management/public/components/management_app/index.ts new file mode 100644 index 0000000000000..83f8ae0159978 --- /dev/null +++ b/src/plugins/management/public/components/management_app/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { ManagementApp, ManagementAppDependencies } from './management_app'; diff --git a/src/plugins/management/public/components/management_app/management_app.scss b/src/plugins/management/public/components/management_app/management_app.scss new file mode 100644 index 0000000000000..00b3e51fb53ee --- /dev/null +++ b/src/plugins/management/public/components/management_app/management_app.scss @@ -0,0 +1,6 @@ + +// Hack because the management wrapper is flat HTML and needs a class +.mgtPage__body { + max-width: map-get($euiBreakpoints, 'xl'); + margin: 0 auto; +} diff --git a/src/plugins/management/public/components/management_app/management_app.tsx b/src/plugins/management/public/components/management_app/management_app.tsx new file mode 100644 index 0000000000000..fc5a8924c95d6 --- /dev/null +++ b/src/plugins/management/public/components/management_app/management_app.tsx @@ -0,0 +1,95 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useState, useEffect, useCallback } from 'react'; +import { + AppMountContext, + AppMountParameters, + ChromeBreadcrumb, + ScopedHistory, +} from 'kibana/public'; +import { I18nProvider } from '@kbn/i18n/react'; +import { EuiPage } from '@elastic/eui'; +import { ManagementStart } from '../../types'; +import { ManagementSection, MANAGEMENT_BREADCRUMB } from '../../utils'; + +import { ManagementRouter } from './management_router'; +import { ManagementSidebarNav } from '../management_sidebar_nav'; +import { reactRouterNavigate } from '../../../../kibana_react/public'; + +import './management_app.scss'; + +interface ManagementAppProps { + appBasePath: string; + context: AppMountContext; + history: AppMountParameters['history']; + dependencies: ManagementAppDependencies; +} + +export interface ManagementAppDependencies { + management: ManagementStart; + kibanaVersion: string; +} + +export const ManagementApp = ({ context, dependencies, history }: ManagementAppProps) => { + const [selectedId, setSelectedId] = useState(''); + const [sections, setSections] = useState(); + + const onAppMounted = useCallback((id: string) => { + setSelectedId(id); + window.scrollTo(0, 0); + }, []); + + const setBreadcrumbs = useCallback( + (crumbs: ChromeBreadcrumb[] = [], appHistory?: ScopedHistory) => { + const wrapBreadcrumb = (item: ChromeBreadcrumb, scopedHistory: ScopedHistory) => ({ + ...item, + ...(item.href ? reactRouterNavigate(scopedHistory, item.href) : {}), + }); + + context.core.chrome.setBreadcrumbs([ + wrapBreadcrumb(MANAGEMENT_BREADCRUMB, history), + ...crumbs.map((item) => wrapBreadcrumb(item, appHistory || history)), + ]); + }, + [context.core.chrome, history] + ); + + useEffect(() => { + setSections(dependencies.management.sections.getSectionsEnabled()); + }, [dependencies.management.sections]); + + if (!sections) { + return null; + } + + return ( + + + + + + + ); +}; diff --git a/src/plugins/management/public/components/management_app/management_router.tsx b/src/plugins/management/public/components/management_app/management_router.tsx new file mode 100644 index 0000000000000..3f934fa68c6ba --- /dev/null +++ b/src/plugins/management/public/components/management_app/management_router.tsx @@ -0,0 +1,72 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { memo } from 'react'; +import { Route, Router, Switch } from 'react-router-dom'; +import { EuiPageBody } from '@elastic/eui'; +import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from 'kibana/public'; +import { ManagementAppWrapper } from '../management_app_wrapper'; +import { ManagementLandingPage } from '../landing'; +import { ManagementAppDependencies } from './management_app'; +import { ManagementSection } from '../../utils'; + +interface ManagementRouterProps { + history: AppMountParameters['history']; + dependencies: ManagementAppDependencies; + setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], appHistory?: ScopedHistory) => void; + onAppMounted: (id: string) => void; + sections: ManagementSection[]; +} + +export const ManagementRouter = memo( + ({ dependencies, history, setBreadcrumbs, onAppMounted, sections }: ManagementRouterProps) => ( + + + + {sections.map((section) => + section + .getAppsEnabled() + .map((app) => ( + ( + + )} + /> + )) + )} + ( + + )} + /> + + + + ) +); diff --git a/src/plugins/management/public/components/management_chrome/index.ts b/src/plugins/management/public/components/management_app_wrapper/index.tsx similarity index 92% rename from src/plugins/management/public/components/management_chrome/index.ts rename to src/plugins/management/public/components/management_app_wrapper/index.tsx index b82c1af871be7..71546e7ca1342 100644 --- a/src/plugins/management/public/components/management_chrome/index.ts +++ b/src/plugins/management/public/components/management_app_wrapper/index.tsx @@ -17,4 +17,4 @@ * under the License. */ -export { ManagementChrome } from './management_chrome'; +export { ManagementAppWrapper } from './management_app_wrapper'; diff --git a/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx b/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx new file mode 100644 index 0000000000000..02da2a46540c2 --- /dev/null +++ b/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx @@ -0,0 +1,69 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { createRef, Component } from 'react'; + +import { ChromeBreadcrumb, AppMountParameters, ScopedHistory } from 'kibana/public'; +import { ManagementApp } from '../../utils'; +import { Unmount } from '../../types'; + +interface ManagementSectionWrapperProps { + app: ManagementApp; + setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], history?: ScopedHistory) => void; + onAppMounted: (id: string) => void; + history: AppMountParameters['history']; +} + +export class ManagementAppWrapper extends Component { + private unmount?: Unmount; + private mountElementRef = createRef(); + + componentDidMount() { + const { setBreadcrumbs, app, onAppMounted, history } = this.props; + const { mount, basePath } = app; + const appHistory = history.createSubHistory(app.basePath); + + const mountResult = mount({ + basePath, + setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => setBreadcrumbs(crumbs, appHistory), + element: this.mountElementRef.current!, + history: appHistory, + }); + + onAppMounted(app.id); + + if (mountResult instanceof Promise) { + mountResult.then((um) => { + this.unmount = um; + }); + } else { + this.unmount = mountResult; + } + } + + async componentWillUnmount() { + if (this.unmount) { + await this.unmount(); + } + } + + render() { + return
; + } +} diff --git a/src/plugins/management/public/components/management_chrome/management_chrome.tsx b/src/plugins/management/public/components/management_chrome/management_chrome.tsx deleted file mode 100644 index df844e2208936..0000000000000 --- a/src/plugins/management/public/components/management_chrome/management_chrome.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import * as React from 'react'; -import { EuiPage, EuiPageBody, EuiPageSideBar } from '@elastic/eui'; -import { I18nProvider } from '@kbn/i18n/react'; -import { ManagementSidebarNav } from '../management_sidebar_nav'; -import { LegacySection } from '../../types'; -import { ManagementSection } from '../../management_section'; - -interface Props { - getSections: () => ManagementSection[]; - legacySections: LegacySection[]; - selectedId: string; - onMounted: (element: HTMLDivElement) => void; -} - -export class ManagementChrome extends React.Component { - private container = React.createRef(); - componentDidMount() { - if (this.container.current) { - this.props.onMounted(this.container.current); - } - } - render() { - return ( - - - - - - -
- - - - ); - } -} diff --git a/src/plugins/management/public/management_sections.tsx b/src/plugins/management/public/components/management_sections.tsx similarity index 93% rename from src/plugins/management/public/management_sections.tsx rename to src/plugins/management/public/components/management_sections.tsx index 77e494626a00e..33c3526c4d23b 100644 --- a/src/plugins/management/public/management_sections.tsx +++ b/src/plugins/management/public/components/management_sections.tsx @@ -20,14 +20,14 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiIcon } from '@elastic/eui'; -import { ManagementSectionId } from './types'; +import { ManagementSectionId } from '../types'; -interface Props { +interface ManagementSectionTitleProps { text: string; tip: string; } -const ManagementSectionTitle = ({ text, tip }: Props) => ( +const ManagementSectionTitle = ({ text, tip }: ManagementSectionTitleProps) => ( {text} diff --git a/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap b/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap deleted file mode 100644 index e7225b356ed68..0000000000000 --- a/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap +++ /dev/null @@ -1,95 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Management adds legacy apps to existing SidebarNav sections 1`] = ` -Array [ - Object { - "data-test-subj": "activeSection", - "icon": null, - "id": "activeSection", - "items": Array [ - Object { - "data-test-subj": "item", - "href": undefined, - "id": "item", - "isSelected": false, - "name": "item", - "order": undefined, - }, - ], - "name": "activeSection", - "order": 10, - }, - Object { - "data-test-subj": "no-active-items", - "icon": null, - "id": "no-active-items", - "items": Array [ - Object { - "data-test-subj": "disabled", - "href": undefined, - "id": "disabled", - "isSelected": false, - "name": "disabled", - "order": undefined, - }, - Object { - "data-test-subj": "notVisible", - "href": undefined, - "id": "notVisible", - "isSelected": false, - "name": "notVisible", - "order": undefined, - }, - ], - "name": "No active items", - "order": 10, - }, -] -`; - -exports[`Management maps legacy sections and apps into SidebarNav items 1`] = ` -Array [ - Object { - "data-test-subj": "no-active-items", - "icon": null, - "id": "no-active-items", - "items": Array [ - Object { - "data-test-subj": "disabled", - "href": undefined, - "id": "disabled", - "isSelected": false, - "name": "disabled", - "order": undefined, - }, - Object { - "data-test-subj": "notVisible", - "href": undefined, - "id": "notVisible", - "isSelected": false, - "name": "notVisible", - "order": undefined, - }, - ], - "name": "No active items", - "order": 10, - }, - Object { - "data-test-subj": "activeSection", - "icon": null, - "id": "activeSection", - "items": Array [ - Object { - "data-test-subj": "item", - "href": undefined, - "id": "item", - "isSelected": false, - "name": "item", - "order": undefined, - }, - ], - "name": "activeSection", - "order": 10, - }, -] -`; diff --git a/src/plugins/management/public/components/management_sidebar_nav/_index.scss b/src/plugins/management/public/components/management_sidebar_nav/_index.scss deleted file mode 100644 index 0a48807344abd..0000000000000 --- a/src/plugins/management/public/components/management_sidebar_nav/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './sidebar_nav'; \ No newline at end of file diff --git a/src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.scss similarity index 84% rename from src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss rename to src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.scss index 5302bd200de3f..a148c1e141e8d 100644 --- a/src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.scss @@ -1,5 +1,6 @@ .mgtSideBarNav { width: 210px; + margin-right: $euiSize; } @include euiBreakpoint('xs','s') { diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts deleted file mode 100644 index e04e0a7572612..0000000000000 --- a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IndexedArray } from '../../../../../legacy/ui/public/indexed_array'; -import { mergeLegacyItems } from './management_sidebar_nav'; - -const toIndexedArray = (initialSet: any[]) => - new IndexedArray({ - index: ['id'], - order: ['order'], - initialSet, - }); - -const activeProps = { visible: true, disabled: false }; -const disabledProps = { visible: true, disabled: true }; -const notVisibleProps = { visible: false, disabled: false }; -const visibleItem = { display: 'item', id: 'item', ...activeProps }; - -const notVisibleSection = { - display: 'Not visible', - id: 'not-visible', - order: 10, - visibleItems: toIndexedArray([visibleItem]), - ...notVisibleProps, -}; -const disabledSection = { - display: 'Disabled', - id: 'disabled', - order: 10, - visibleItems: toIndexedArray([visibleItem]), - ...disabledProps, -}; -const noItemsSection = { - display: 'No items', - id: 'no-items', - order: 10, - visibleItems: toIndexedArray([]), - ...activeProps, -}; -const noActiveItemsSection = { - display: 'No active items', - id: 'no-active-items', - order: 10, - visibleItems: toIndexedArray([ - { display: 'disabled', id: 'disabled', ...disabledProps }, - { display: 'notVisible', id: 'notVisible', ...notVisibleProps }, - ]), - ...activeProps, -}; -const activeSection = { - display: 'activeSection', - id: 'activeSection', - order: 10, - visibleItems: toIndexedArray([visibleItem]), - ...activeProps, -}; - -const managementSections = [ - notVisibleSection, - disabledSection, - noItemsSection, - noActiveItemsSection, - activeSection, -]; - -describe('Management', () => { - it('maps legacy sections and apps into SidebarNav items', () => { - expect(mergeLegacyItems([], managementSections, 'active-item-id')).toMatchSnapshot(); - }); - - it('adds legacy apps to existing SidebarNav sections', () => { - const navSection = { - 'data-test-subj': 'activeSection', - icon: null, - id: 'activeSection', - items: [], - name: 'activeSection', - order: 10, - }; - expect(mergeLegacyItems([navSection], managementSections, 'active-item-id')).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx index 208f577b76996..055dda5ed84a1 100644 --- a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx @@ -17,184 +17,97 @@ * under the License. */ -import { - EuiIcon, - // @ts-ignore - EuiSideNav, - EuiScreenReaderOnly, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import React, { ReactElement } from 'react'; -import { LegacySection, LegacyApp } from '../../types'; -import { ManagementApp } from '../../management_app'; -import { ManagementSection } from '../../management_section'; - -interface NavApp { - id: string; - name: ReactElement | string; - [key: string]: unknown; - order: number; // only needed while merging platform and legacy -} +import { sortBy } from 'lodash'; -interface NavSection extends NavApp { - items: NavApp[]; -} +import { EuiIcon, EuiSideNav, EuiScreenReaderOnly, EuiSideNavItemType } from '@elastic/eui'; +import { AppMountParameters } from 'kibana/public'; +import { ManagementApp, ManagementSection } from '../../utils'; + +import './management_sidebar_nav.scss'; + +import { ManagementItem } from '../../utils/management_item'; +import { reactRouterNavigate } from '../../../../kibana_react/public'; interface ManagementSidebarNavProps { - getSections: () => ManagementSection[]; - legacySections: LegacySection[]; + sections: ManagementSection[]; + history: AppMountParameters['history']; selectedId: string; } -interface ManagementSidebarNavState { - isSideNavOpenOnMobile: boolean; -} - -const managementSectionOrAppToNav = (appOrSection: ManagementApp | ManagementSection) => ({ - id: appOrSection.id, - name: appOrSection.title, - 'data-test-subj': appOrSection.id, - order: appOrSection.order, -}); - -const managementSectionToNavSection = (section: ManagementSection) => { - const iconType = section.euiIconType - ? section.euiIconType - : section.icon - ? section.icon - : 'empty'; - - return { - icon: , - ...managementSectionOrAppToNav(section), - }; -}; - -const managementAppToNavItem = (selectedId?: string, parentId?: string) => ( - app: ManagementApp -) => ({ - isSelected: selectedId === app.id, - href: `#/management/${parentId}/${app.id}`, - ...managementSectionOrAppToNav(app), +const headerLabel = i18n.translate('management.nav.label', { + defaultMessage: 'Management', }); -const legacySectionToNavSection = (section: LegacySection) => ({ - name: section.display, - id: section.id, - icon: section.icon ? : null, - items: [], - 'data-test-subj': section.id, - // @ts-ignore - order: section.order, +const navMenuLabel = i18n.translate('management.nav.menu', { + defaultMessage: 'Management menu', }); -const legacyAppToNavItem = (app: LegacyApp, selectedId: string) => ({ - isSelected: selectedId === app.id, - name: app.display, - id: app.id, - href: app.url, - 'data-test-subj': app.id, - // @ts-ignore - order: app.order, -}); - -const sectionVisible = (section: LegacySection | LegacyApp) => !section.disabled && section.visible; - -const sideNavItems = (sections: ManagementSection[], selectedId: string) => - sections.map((section) => ({ - items: section.getAppsEnabled().map(managementAppToNavItem(selectedId, section.id)), - ...managementSectionToNavSection(section), - })); - -const findOrAddSection = (navItems: NavSection[], legacySection: LegacySection): NavSection => { - const foundSection = navItems.find((sec) => sec.id === legacySection.id); - - if (foundSection) { - return foundSection; - } else { - const newSection = legacySectionToNavSection(legacySection); - navItems.push(newSection); - navItems.sort((a: NavSection, b: NavSection) => a.order - b.order); // only needed while merging platform and legacy - return newSection; - } -}; - -export const mergeLegacyItems = ( - navItems: NavSection[], - legacySections: LegacySection[], - selectedId: string -) => { - const filteredLegacySections = legacySections - .filter(sectionVisible) - .filter((section) => section.visibleItems.length); - - filteredLegacySections.forEach((legacySection) => { - const section = findOrAddSection(navItems, legacySection); - legacySection.visibleItems.forEach((app) => { - section.items.push(legacyAppToNavItem(app, selectedId)); - return section.items.sort((a, b) => a.order - b.order); - }); - }); - - return navItems; -}; - -const sectionsToItems = ( - sections: ManagementSection[], - legacySections: LegacySection[], - selectedId: string -) => { - const navItems = sideNavItems(sections, selectedId); - return mergeLegacyItems(navItems, legacySections, selectedId); -}; +/** @internal **/ +export const ManagementSidebarNav = ({ + selectedId, + sections, + history, +}: ManagementSidebarNavProps) => { + const HEADER_ID = 'stack-management-nav-header'; + const [isSideNavOpenOnMobile, setIsSideNavOpenOnMobile] = useState(false); + const toggleOpenOnMobile = () => setIsSideNavOpenOnMobile(!isSideNavOpenOnMobile); + + const sectionsToNavItems = (managementSections: ManagementSection[]) => { + const sortedManagementSections = sortBy(managementSections, 'order'); + + return sortedManagementSections.reduce>>((acc, section) => { + const apps = sortBy(section.getAppsEnabled(), 'order'); + + if (apps.length) { + acc.push({ + ...createNavItem(section, { + items: appsToNavItems(apps), + }), + }); + } + + return acc; + }, []); + }; -export class ManagementSidebarNav extends React.Component< - ManagementSidebarNavProps, - ManagementSidebarNavState -> { - constructor(props: ManagementSidebarNavProps) { - super(props); - this.state = { - isSideNavOpenOnMobile: false, + const appsToNavItems = (managementApps: ManagementApp[]) => + managementApps.map((app) => ({ + ...createNavItem(app, { + ...reactRouterNavigate(history, app.basePath), + }), + })); + + const createNavItem = ( + item: T, + customParams: Partial> = {} + ) => { + const iconType = item.euiIconType || item.icon; + + return { + id: item.id, + name: item.title, + isSelected: item.id === selectedId, + icon: iconType ? : undefined, + 'data-test-subj': item.id, + ...customParams, }; - } - - public render() { - const HEADER_ID = 'stack-management-nav-header'; - - return ( - <> - -

- {i18n.translate('management.nav.label', { - defaultMessage: 'Management', - })} -

-
- - - ); - } - - private renderMobileTitle() { - return ; - } - - private toggleOpenOnMobile = () => { - this.setState({ - isSideNavOpenOnMobile: !this.state.isSideNavOpenOnMobile, - }); }; -} + + return ( + <> + +

{headerLabel}

+
+ + + ); +}; diff --git a/src/plugins/management/public/index.ts b/src/plugins/management/public/index.ts index f2cc6a00b93d8..3ba469c7831f6 100644 --- a/src/plugins/management/public/index.ts +++ b/src/plugins/management/public/index.ts @@ -21,17 +21,14 @@ import { PluginInitializerContext } from 'kibana/public'; import { ManagementPlugin } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { - return new ManagementPlugin(); + return new ManagementPlugin(initializerContext); } +export { RegisterManagementAppArgs, ManagementSection, ManagementApp } from './utils'; + export { - ManagementSetup, - ManagementStart, - RegisterManagementApp, ManagementSectionId, - RegisterManagementAppArgs, ManagementAppMountParams, + ManagementSetup, + ManagementStart, } from './types'; -export { ManagementApp } from './management_app'; -export { ManagementSection } from './management_section'; -export { ManagementSidebarNav } from './components'; // for use in legacy management apps diff --git a/src/plugins/management/public/legacy/redirect_messages.tsx b/src/plugins/management/public/legacy/redirect_messages.tsx deleted file mode 100644 index f8cb975e6fae5..0000000000000 --- a/src/plugins/management/public/legacy/redirect_messages.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { EuiCallOut } from '@elastic/eui'; -import { NotificationsStart, OverlayStart } from 'kibana/public'; -import { parse } from 'query-string'; -import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '../../../kibana_react/public'; -import { MarkdownSimple } from '../../../kibana_react/public'; - -/** - * Show banners and toasts carried over from other applications. This is only necessary as long as - * management is rendered in the legacy platform (which requires a full page reload to switch to). - * - * Once management is rendered using the core application service, this file and the places setting - * bannerMessage and notFoundMessage URL params can be removed. - * @param notifications Core notifications service - * @param overlays Core overlays service - */ -export function showLegacyRedirectMessages( - notifications: NotificationsStart, - overlays: OverlayStart -) { - const queryPosition = window.location.hash.indexOf('?'); - if (queryPosition === -1) { - return; - } - - const urlParams = parse(window.location.hash.substr(queryPosition)) as Record; - - if (urlParams.bannerMessage) { - const bannerId = overlays.banners.add( - toMountPoint( - - ) - ); - setTimeout(() => { - overlays.banners.remove(bannerId); - }, 15000); - } - - if (urlParams.notFoundMessage) { - notifications.toasts.addWarning({ - title: i18n.translate('management.history.savedObjectIsMissingNotificationMessage', { - defaultMessage: 'Saved object is missing', - }), - text: toMountPoint({urlParams.notFoundMessage}), - }); - } -} diff --git a/src/plugins/management/public/legacy/section.js b/src/plugins/management/public/legacy/section.js deleted file mode 100644 index 5b39f350bf444..0000000000000 --- a/src/plugins/management/public/legacy/section.js +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { assign } from 'lodash'; -import { IndexedArray } from '../../../../legacy/ui/public/indexed_array'; - -const listeners = []; - -export class LegacyManagementSection { - /** - * @param {string} id - * @param {object} options - * @param {number|null} options.order - * @param {string|null} options.display - defaults to id - * @param {string|null} options.url - defaults to '' - * @param {boolean|null} options.visible - defaults to true - * @param {boolean|null} options.disabled - defaults to false - * @param {string|null} options.tooltip - defaults to '' - * @param {string|null} options.icon - defaults to '' - * @returns {ManagementSection} - */ - - constructor(id, options = {}, capabilities) { - this.display = id; - this.id = id; - this.items = new IndexedArray({ - index: ['id'], - order: ['order'], - }); - this.visible = true; - this.disabled = false; - this.tooltip = ''; - this.icon = ''; - this.url = ''; - this.capabilities = capabilities; - - assign(this, options); - } - - get visibleItems() { - return this.items.inOrder.filter((item) => { - const capabilityManagementSection = this.capabilities.management[this.id]; - const itemCapability = capabilityManagementSection - ? capabilityManagementSection[item.id] - : null; - - return item.visible && itemCapability !== false; - }); - } - - /** - * Registers a callback that will be executed when management sections are updated - * Globally bound to solve for sidebar nav needs - * - * @param {function} fn - */ - addListener(fn) { - listeners.push(fn); - } - - /** - * Registers a sub-section - * - * @param {string} id - * @param {object} options - * @returns {ManagementSection} - */ - - register(id, options = {}) { - const item = new LegacyManagementSection( - id, - assign(options, { parent: this }), - this.capabilities - ); - - if (this.hasItem(id)) { - throw new Error(`'${id}' is already registered`); - } - - this.items.push(item); - listeners.forEach((fn) => fn()); - - return item; - } - - /** - * Deregisters a section - * - * @param {string} id - */ - deregister(id) { - this.items.remove((item) => item.id === id); - listeners.forEach((fn) => fn(this.items)); - } - - /** - * Determine if an id is already registered - * - * @param {string} id - * @returns {boolean} - */ - - hasItem(id) { - return this.items.byId.hasOwnProperty(id); - } - - /** - * Fetches a section by id - * - * @param {string} id - * @returns {ManagementSection} - */ - - getSection(id) { - if (!id) { - return; - } - - const sectionPath = id.split('/'); - return sectionPath.reduce((currentSection, nextSection) => { - if (!currentSection) { - return; - } - - return currentSection.items.byId[nextSection]; - }, this); - } - - hide() { - this.visible = false; - } - - show() { - this.visible = true; - } - - disable() { - this.disabled = true; - } - - enable() { - this.disabled = false; - } -} diff --git a/src/plugins/management/public/legacy/section.test.js b/src/plugins/management/public/legacy/section.test.js deleted file mode 100644 index bf75506a218d5..0000000000000 --- a/src/plugins/management/public/legacy/section.test.js +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { LegacyManagementSection } from './section'; -import { IndexedArray } from '../../../../legacy/ui/public/indexed_array'; - -const capabilitiesMock = { - management: { - kibana: { sampleFeature2: false }, - }, -}; - -describe('ManagementSection', () => { - describe('constructor', () => { - it('defaults display to id', () => { - const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - expect(section.display).toBe('kibana'); - }); - - it('defaults visible to true', () => { - const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - expect(section.visible).toBe(true); - }); - - it('defaults disabled to false', () => { - const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - expect(section.disabled).toBe(false); - }); - - it('defaults tooltip to empty string', () => { - const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - expect(section.tooltip).toBe(''); - }); - - it('defaults url to empty string', () => { - const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - expect(section.url).toBe(''); - }); - - it('exposes items', () => { - const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - expect(section.items).toHaveLength(0); - }); - - it('exposes visibleItems', () => { - const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - expect(section.visibleItems).toHaveLength(0); - }); - - it('assigns all options', () => { - const section = new LegacyManagementSection( - 'kibana', - { description: 'test', url: 'foobar' }, - capabilitiesMock - ); - expect(section.description).toBe('test'); - expect(section.url).toBe('foobar'); - }); - }); - - describe('register', () => { - let section; - - beforeEach(() => { - section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - }); - - it('returns a ManagementSection', () => { - expect(section.register('about')).toBeInstanceOf(LegacyManagementSection); - }); - - it('provides a reference to the parent', () => { - expect(section.register('about').parent).toBe(section); - }); - - it('adds item', function () { - section.register('about', { description: 'test' }); - - expect(section.items).toHaveLength(1); - expect(section.items[0]).toBeInstanceOf(LegacyManagementSection); - expect(section.items[0].id).toBe('about'); - }); - - it('can only register a section once', () => { - let threwException = false; - section.register('about'); - - try { - section.register('about'); - } catch (e) { - threwException = e.message.indexOf('is already registered') > -1; - } - - expect(threwException).toBe(true); - }); - - it('calls listener when item added', () => { - let listerCalled = false; - const listenerFn = () => { - listerCalled = true; - }; - - section.addListener(listenerFn); - section.register('about'); - expect(listerCalled).toBe(true); - }); - }); - - describe('deregister', () => { - let section; - - beforeEach(() => { - section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - section.register('about'); - }); - - it('deregisters an existing section', () => { - section.deregister('about'); - expect(section.items).toHaveLength(0); - }); - - it('allows deregistering a section more than once', () => { - section.deregister('about'); - section.deregister('about'); - expect(section.items).toHaveLength(0); - }); - - it('calls listener when item added', () => { - let listerCalled = false; - const listenerFn = () => { - listerCalled = true; - }; - - section.addListener(listenerFn); - section.deregister('about'); - expect(listerCalled).toBe(true); - }); - }); - - describe('getSection', () => { - let section; - - beforeEach(() => { - section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - section.register('about'); - }); - - it('returns registered section', () => { - expect(section.getSection('about')).toBeInstanceOf(LegacyManagementSection); - }); - - it('returns undefined if un-registered', () => { - expect(section.getSection('unknown')).not.toBeDefined(); - }); - - it('returns sub-sections specified via a /-separated path', () => { - section.getSection('about').register('time'); - expect(section.getSection('about/time')).toBeInstanceOf(LegacyManagementSection); - expect(section.getSection('about/time')).toBe(section.getSection('about').getSection('time')); - }); - - it('returns undefined if a sub-section along a /-separated path does not exist', () => { - expect(section.getSection('about/damn/time')).toBe(undefined); - }); - }); - - describe('items', () => { - let section; - - beforeEach(() => { - section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - - section.register('three', { order: 3 }); - section.register('one', { order: 1 }); - section.register('two', { order: 2 }); - }); - - it('is an indexed array', () => { - expect(section.items).toBeInstanceOf(IndexedArray); - }); - - it('is indexed on id', () => { - const keys = Object.keys(section.items.byId).sort(); - expect(section.items.byId).toBeInstanceOf(Object); - - expect(keys).toEqual(['one', 'three', 'two']); - }); - - it('can be ordered', () => { - const ids = section.items.inOrder.map((i) => { - return i.id; - }); - expect(ids).toEqual(['one', 'two', 'three']); - }); - }); - - describe('visible', () => { - let section; - - beforeEach(() => { - section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - }); - - it('hide sets visible to false', () => { - section.hide(); - expect(section.visible).toBe(false); - }); - - it('show sets visible to true', () => { - section.hide(); - section.show(); - expect(section.visible).toBe(true); - }); - }); - - describe('disabled', () => { - let section; - - beforeEach(() => { - section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - }); - - it('disable sets disabled to true', () => { - section.disable(); - expect(section.disabled).toBe(true); - }); - - it('enable sets disabled to false', () => { - section.enable(); - expect(section.disabled).toBe(false); - }); - }); - - describe('visibleItems', () => { - let section; - - beforeEach(() => { - section = new LegacyManagementSection('kibana', {}, capabilitiesMock); - - section.register('three', { order: 3 }); - section.register('one', { order: 1 }); - section.register('two', { order: 2 }); - }); - - it('maintains the order', () => { - const ids = section.visibleItems.map((i) => { - return i.id; - }); - expect(ids).toEqual(['one', 'two', 'three']); - }); - - it('does not include hidden items', () => { - section.getSection('two').hide(); - - const ids = section.visibleItems.map((i) => { - return i.id; - }); - expect(ids).toEqual(['one', 'three']); - }); - - it('does not include visible items hidden via uiCapabilities', () => { - section.register('sampleFeature2', { order: 4, visible: true }); - const ids = section.visibleItems.map((i) => { - return i.id; - }); - expect(ids).toEqual(['one', 'two', 'three']); - }); - }); -}); diff --git a/src/plugins/management/public/legacy/sections_register.js b/src/plugins/management/public/legacy/sections_register.js deleted file mode 100644 index d77f87e80ea18..0000000000000 --- a/src/plugins/management/public/legacy/sections_register.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import { LegacyManagementSection } from './section'; -import { managementSections } from '../management_sections'; - -export class LegacyManagementAdapter { - main = undefined; - init = (capabilities) => { - this.main = new LegacyManagementSection( - 'management', - { - display: i18n.translate('management.displayName', { - defaultMessage: 'Stack Management', - }), - }, - capabilities - ); - - managementSections.forEach(({ id, title }, idx) => { - this.main.register(id, { - display: title, - order: idx, - }); - }); - - return this.main; - }; - getManagement = () => this.main; -} diff --git a/src/plugins/management/public/management_app.test.tsx b/src/plugins/management/public/management_app.test.tsx deleted file mode 100644 index a76b234d95ef5..0000000000000 --- a/src/plugins/management/public/management_app.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { coreMock } from '../../../core/public/mocks'; - -import { ManagementApp } from './management_app'; -// @ts-ignore -import { LegacyManagementSection } from './legacy'; - -function createTestApp() { - const legacySection = new LegacyManagementSection('legacy'); - return new ManagementApp( - { - id: 'test-app', - title: 'Test App', - basePath: '', - mount(params) { - params.setBreadcrumbs([{ text: 'Test App' }]); - ReactDOM.render(
Test App - Hello world!
, params.element); - - return () => { - ReactDOM.unmountComponentAtNode(params.element); - }; - }, - }, - () => [], - jest.fn(), - () => legacySection, - coreMock.createSetup().getStartServices - ); -} - -test('Management app can mount and unmount', async () => { - const testApp = createTestApp(); - const container = document.createElement('div'); - document.body.appendChild(container); - const unmount = testApp.mount({ element: container, basePath: '', setBreadcrumbs: jest.fn() }); - expect(container).toMatchSnapshot(); - (await unmount)(); - expect(container).toMatchSnapshot(); -}); - -test('Enabled by default, can disable', () => { - const testApp = createTestApp(); - expect(testApp.enabled).toBe(true); - testApp.disable(); - expect(testApp.enabled).toBe(false); -}); diff --git a/src/plugins/management/public/management_app.tsx b/src/plugins/management/public/management_app.tsx deleted file mode 100644 index 2954cefa86d5c..0000000000000 --- a/src/plugins/management/public/management_app.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import * as React from 'react'; -import ReactDOM from 'react-dom'; -import { i18n } from '@kbn/i18n'; -import { CreateManagementApp, ManagementSectionMount, Unmount } from './types'; -import { KibanaLegacySetup } from '../../kibana_legacy/public'; -// @ts-ignore -import { LegacyManagementSection } from './legacy'; -import { ManagementChrome } from './components'; -import { ManagementSection } from './management_section'; -import { ChromeBreadcrumb, StartServicesAccessor } from '../../../core/public/'; - -export class ManagementApp { - readonly id: string; - readonly title: string; - readonly basePath: string; - readonly order: number; - readonly mount: ManagementSectionMount; - private enabledStatus = true; - - constructor( - { id, title, basePath, order = 100, mount }: CreateManagementApp, - getSections: () => ManagementSection[], - registerLegacyApp: KibanaLegacySetup['registerLegacyApp'], - getLegacyManagementSections: () => LegacyManagementSection, - getStartServices: StartServicesAccessor - ) { - this.id = id; - this.title = title; - this.basePath = basePath; - this.order = order; - this.mount = mount; - - registerLegacyApp({ - id: basePath.substr(1), // get rid of initial slash - title, - mount: async ({}, params) => { - let appUnmount: Unmount; - if (!this.enabledStatus) { - const [coreStart] = await getStartServices(); - coreStart.application.navigateToApp('kibana#/management'); - return () => {}; - } - async function setBreadcrumbs(crumbs: ChromeBreadcrumb[]) { - const [coreStart] = await getStartServices(); - coreStart.chrome.setBreadcrumbs([ - { - text: i18n.translate('management.breadcrumb', { - defaultMessage: 'Stack Management', - }), - href: '#/management', - }, - ...crumbs, - ]); - } - - ReactDOM.render( - { - appUnmount = await mount({ - basePath, - element, - setBreadcrumbs, - }); - }} - />, - params.element - ); - - return async () => { - appUnmount(); - ReactDOM.unmountComponentAtNode(params.element); - }; - }, - }); - } - public enable() { - this.enabledStatus = true; - } - public disable() { - this.enabledStatus = false; - } - public get enabled() { - return this.enabledStatus; - } -} diff --git a/src/plugins/management/public/management_section.test.ts b/src/plugins/management/public/management_section.test.ts deleted file mode 100644 index e1d047425ac18..0000000000000 --- a/src/plugins/management/public/management_section.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ManagementSection } from './management_section'; -import { ManagementSectionId } from './types'; -// @ts-ignore -import { LegacyManagementSection } from './legacy'; -import { coreMock } from '../../../core/public/mocks'; - -function createSection(registerLegacyApp: () => void) { - const legacySection = new LegacyManagementSection('legacy'); - const getLegacySection = () => legacySection; - const getManagementSections: () => ManagementSection[] = () => []; - - const testSectionConfig = { id: ManagementSectionId.Data, title: 'Test Section' }; - return new ManagementSection( - testSectionConfig, - getManagementSections, - registerLegacyApp, - getLegacySection, - coreMock.createSetup().getStartServices - ); -} - -test('cannot register two apps with the same id', () => { - const registerLegacyApp = jest.fn(); - const section = createSection(registerLegacyApp); - - const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} }; - - section.registerApp(testAppConfig); - expect(registerLegacyApp).toHaveBeenCalled(); - expect(section.apps.length).toEqual(1); - - expect(() => { - section.registerApp(testAppConfig); - }).toThrow(); -}); - -test('can enable and disable apps', () => { - const registerLegacyApp = jest.fn(); - const section = createSection(registerLegacyApp); - - const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} }; - - const app = section.registerApp(testAppConfig); - expect(section.getAppsEnabled().length).toEqual(1); - app.disable(); - expect(section.getAppsEnabled().length).toEqual(0); -}); diff --git a/src/plugins/management/public/management_section.ts b/src/plugins/management/public/management_section.ts deleted file mode 100644 index 80ef1a108ecd8..0000000000000 --- a/src/plugins/management/public/management_section.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ReactElement } from 'react'; - -import { CreateSection, RegisterManagementAppArgs, ManagementSectionId } from './types'; -import { KibanaLegacySetup } from '../../kibana_legacy/public'; -import { StartServicesAccessor } from '../../../core/public'; -// @ts-ignore -import { LegacyManagementSection } from './legacy'; -import { ManagementApp } from './management_app'; - -export class ManagementSection { - public readonly id: ManagementSectionId; - public readonly title: string | ReactElement = ''; - public readonly apps: ManagementApp[] = []; - public readonly order: number; - public readonly euiIconType?: string; - public readonly icon?: string; - private readonly getSections: () => ManagementSection[]; - private readonly registerLegacyApp: KibanaLegacySetup['registerLegacyApp']; - private readonly getLegacyManagementSection: () => LegacyManagementSection; - private readonly getStartServices: StartServicesAccessor; - - constructor( - { id, title, order = 100, euiIconType, icon }: CreateSection, - getSections: () => ManagementSection[], - registerLegacyApp: KibanaLegacySetup['registerLegacyApp'], - getLegacyManagementSection: () => ManagementSection, - getStartServices: StartServicesAccessor - ) { - this.id = id; - this.title = title; - this.order = order; - this.euiIconType = euiIconType; - this.icon = icon; - this.getSections = getSections; - this.registerLegacyApp = registerLegacyApp; - this.getLegacyManagementSection = getLegacyManagementSection; - this.getStartServices = getStartServices; - } - - registerApp({ id, title, order, mount }: RegisterManagementAppArgs) { - if (this.getApp(id)) { - throw new Error(`Management app already registered - id: ${id}, title: ${title}`); - } - - const app = new ManagementApp( - { id, title, order, mount, basePath: `/management/${this.id}/${id}` }, - this.getSections, - this.registerLegacyApp, - this.getLegacyManagementSection, - this.getStartServices - ); - this.apps.push(app); - return app; - } - getApp(id: ManagementApp['id']) { - return this.apps.find((app) => app.id === id); - } - getAppsEnabled() { - return this.apps.filter((app) => app.enabled).sort((a, b) => a.order - b.order); - } -} diff --git a/src/plugins/management/public/management_sections_service.test.ts b/src/plugins/management/public/management_sections_service.test.ts new file mode 100644 index 0000000000000..2c5d04883235c --- /dev/null +++ b/src/plugins/management/public/management_sections_service.test.ts @@ -0,0 +1,59 @@ +/* + * 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 { ManagementSectionId } from './index'; +import { ManagementSectionsService } from './management_sections_service'; + +describe('ManagementService', () => { + let managementService: ManagementSectionsService; + + beforeEach(() => { + managementService = new ManagementSectionsService(); + }); + + test('Provides default sections', () => { + managementService.setup(); + const start = managementService.start(); + + expect(start.getAllSections().length).toEqual(6); + expect(start.getSection(ManagementSectionId.Ingest)).toBeDefined(); + expect(start.getSection(ManagementSectionId.Data)).toBeDefined(); + expect(start.getSection(ManagementSectionId.InsightsAndAlerting)).toBeDefined(); + expect(start.getSection(ManagementSectionId.Security)).toBeDefined(); + expect(start.getSection(ManagementSectionId.Kibana)).toBeDefined(); + expect(start.getSection(ManagementSectionId.Stack)).toBeDefined(); + }); + + test('Register section, enable and disable', () => { + // Setup phase: + const setup = managementService.setup(); + const testSection = setup.register({ id: 'test-section', title: 'Test Section' }); + + expect(setup.getSection('test-section')).not.toBeUndefined(); + + // Start phase: + const start = managementService.start(); + + expect(start.getSectionsEnabled().length).toEqual(7); + + testSection.disable(); + + expect(start.getSectionsEnabled().length).toEqual(6); + }); +}); diff --git a/src/plugins/management/public/management_sections_service.ts b/src/plugins/management/public/management_sections_service.ts new file mode 100644 index 0000000000000..08a87b3e89f2b --- /dev/null +++ b/src/plugins/management/public/management_sections_service.ts @@ -0,0 +1,65 @@ +/* + * 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 { ReactElement } from 'react'; +import { ManagementSection, RegisterManagementSectionArgs } from './utils'; +import { managementSections } from './components/management_sections'; + +import { ManagementSectionId, SectionsServiceSetup, SectionsServiceStart } from './types'; + +export class ManagementSectionsService { + private sections: Map = new Map(); + + private getSection = (sectionId: ManagementSectionId | string) => + this.sections.get(sectionId) as ManagementSection; + + private getAllSections = () => [...this.sections.values()]; + + private registerSection = (section: RegisterManagementSectionArgs) => { + if (this.sections.has(section.id)) { + throw Error(`ManagementSection '${section.id}' already registered`); + } + + const newSection = new ManagementSection(section); + + this.sections.set(section.id, newSection); + return newSection; + }; + + setup(): SectionsServiceSetup { + managementSections.forEach( + ({ id, title }: { id: ManagementSectionId; title: ReactElement }, idx: number) => { + this.registerSection({ id, title, order: idx }); + } + ); + + return { + register: this.registerSection, + getSection: this.getSection, + }; + } + + start(): SectionsServiceStart { + return { + getSection: this.getSection, + getAllSections: this.getAllSections, + getSectionsEnabled: () => this.getAllSections().filter((section) => section.enabled), + }; + } +} diff --git a/src/plugins/management/public/management_service.test.ts b/src/plugins/management/public/management_service.test.ts deleted file mode 100644 index 1507d6f43619d..0000000000000 --- a/src/plugins/management/public/management_service.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ManagementService } from './management_service'; -import { ManagementSectionId } from './types'; -import { coreMock } from '../../../core/public/mocks'; -import { npSetup } from '../../../legacy/ui/public/new_platform/__mocks__'; - -jest.mock('ui/new_platform'); - -test('Provides default sections', () => { - const service = new ManagementService().setup( - npSetup.plugins.kibanaLegacy, - () => {}, - coreMock.createSetup().getStartServices - ); - expect(service.getAllSections().length).toEqual(6); - expect(service.getSection(ManagementSectionId.Ingest)).toBeDefined(); - expect(service.getSection(ManagementSectionId.Data)).toBeDefined(); - expect(service.getSection(ManagementSectionId.InsightsAndAlerting)).toBeDefined(); - expect(service.getSection(ManagementSectionId.Security)).toBeDefined(); - expect(service.getSection(ManagementSectionId.Kibana)).toBeDefined(); - expect(service.getSection(ManagementSectionId.Stack)).toBeDefined(); -}); diff --git a/src/plugins/management/public/management_service.ts b/src/plugins/management/public/management_service.ts deleted file mode 100644 index 84939fe095336..0000000000000 --- a/src/plugins/management/public/management_service.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ReactElement } from 'react'; - -import { ManagementSection } from './management_section'; -import { managementSections } from './management_sections'; -import { KibanaLegacySetup } from '../../kibana_legacy/public'; -// @ts-ignore -import { LegacyManagementSection, sections } from './legacy'; -import { CreateSection, ManagementSectionId } from './types'; -import { StartServicesAccessor, CoreStart } from '../../../core/public'; - -export class ManagementService { - private sections: ManagementSection[] = []; - - private register( - registerLegacyApp: KibanaLegacySetup['registerLegacyApp'], - getLegacyManagement: () => LegacyManagementSection, - getStartServices: StartServicesAccessor - ) { - return (section: CreateSection) => { - if (this.getSection(section.id)) { - throw Error(`ManagementSection '${section.id}' already registered`); - } - - const newSection = new ManagementSection( - section, - this.getSectionsEnabled.bind(this), - registerLegacyApp, - getLegacyManagement, - getStartServices - ); - this.sections.push(newSection); - return newSection; - }; - } - - private getSection(sectionId: ManagementSectionId) { - return this.sections.find((section) => section.id === sectionId); - } - - private getAllSections() { - return this.sections; - } - - private getSectionsEnabled() { - return this.sections - .filter((section) => section.getAppsEnabled().length > 0) - .sort((a, b) => a.order - b.order); - } - - private sharedInterface = { - getSection: (sectionId: ManagementSectionId) => { - const section = this.getSection(sectionId); - if (!section) { - throw new Error(`Management section with id ${sectionId} is undefined`); - } - return section; - }, - getSectionsEnabled: this.getSectionsEnabled.bind(this), - getAllSections: this.getAllSections.bind(this), - }; - - public setup( - kibanaLegacy: KibanaLegacySetup, - getLegacyManagement: () => LegacyManagementSection, - getStartServices: StartServicesAccessor - ) { - const register = this.register.bind(this)( - kibanaLegacy.registerLegacyApp, - getLegacyManagement, - getStartServices - ); - - managementSections.forEach( - ({ id, title }: { id: ManagementSectionId; title: ReactElement }, idx: number) => { - register({ id, title, order: idx }); - } - ); - - return { - ...this.sharedInterface, - }; - } - - public start(navigateToApp: CoreStart['application']['navigateToApp']) { - return { - navigateToApp, // apps are currently registered as top level apps but this may change in the future - ...this.sharedInterface, - }; - } -} diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index 3e32ff4fe26b2..123e3f28877aa 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -18,29 +18,29 @@ */ import { ManagementSetup, ManagementStart } from '../types'; -import { ManagementSection } from '../management_section'; +import { ManagementSection } from '../index'; -const createManagementSectionMock = (): jest.Mocked> => { - return { +const createManagementSectionMock = () => + (({ + disable: jest.fn(), + enable: jest.fn(), registerApp: jest.fn(), getApp: jest.fn(), - getAppsEnabled: jest.fn().mockReturnValue([]), - }; -}; + getEnabledItems: jest.fn().mockReturnValue([]), + } as unknown) as ManagementSection); const createSetupContract = (): DeeplyMockedKeys => ({ sections: { + register: jest.fn(), getSection: jest.fn().mockReturnValue(createManagementSectionMock()), - getAllSections: jest.fn().mockReturnValue([]), }, }); const createStartContract = (): DeeplyMockedKeys => ({ - legacy: {}, sections: { getSection: jest.fn(), getAllSections: jest.fn(), - navigateToApp: jest.fn(), + getSectionsEnabled: jest.fn(), }, }); diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index e7f86996a9a1b..71656d7c0b83b 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -18,23 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { ManagementSetup, ManagementStart } from './types'; -import { ManagementService } from './management_service'; -import { KibanaLegacySetup } from '../../kibana_legacy/public'; import { FeatureCatalogueCategory, HomePublicPluginSetup } from '../../home/public'; -// @ts-ignore -import { LegacyManagementAdapter } from './legacy'; -import { showLegacyRedirectMessages } from './legacy/redirect_messages'; +import { + CoreSetup, + CoreStart, + Plugin, + DEFAULT_APP_CATEGORIES, + PluginInitializerContext, +} from '../../../core/public'; + +import { ManagementSectionsService } from './management_sections_service'; + +interface ManagementSetupDependencies { + home: HomePublicPluginSetup; +} export class ManagementPlugin implements Plugin { - private managementSections = new ManagementService(); - private legacyManagement = new LegacyManagementAdapter(); + private readonly managementSections = new ManagementSectionsService(); + + constructor(private initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, { home }: ManagementSetupDependencies) { + const kibanaVersion = this.initializerContext.env.packageInfo.version; - public setup( - core: CoreSetup, - { kibanaLegacy, home }: { kibanaLegacy: KibanaLegacySetup; home: HomePublicPluginSetup } - ) { home.featureCatalogue.register({ id: 'stack-management', title: i18n.translate('management.stackManagement.managementLabel', { @@ -44,25 +51,38 @@ export class ManagementPlugin implements Plugin ManagementSection; + getSection: (sectionId: ManagementSectionId | string) => ManagementSection; +} + +export interface SectionsServiceStart { + getSection: (sectionId: ManagementSectionId | string) => ManagementSection; + getAllSections: () => ManagementSection[]; + getSectionsEnabled: () => ManagementSection[]; } export enum ManagementSectionId { @@ -60,67 +50,20 @@ export enum ManagementSectionId { Stack = 'stack', } -interface SectionsServiceSetup { - getSection: (sectionId: ManagementSectionId) => ManagementSection; - getAllSections: () => ManagementSection[]; -} - -interface SectionsServiceStart { - getSection: (sectionId: ManagementSectionId) => ManagementSection; - getAllSections: () => ManagementSection[]; - navigateToApp: ApplicationStart['navigateToApp']; -} - -export interface CreateSection { - id: ManagementSectionId; - title: string | ReactElement; - order?: number; - euiIconType?: string; // takes precedence over `icon` property. - icon?: string; // URL to image file; fallback if no `euiIconType` -} - -export type RegisterSection = (section: CreateSection) => ManagementSection; - -export interface RegisterManagementAppArgs { - id: string; - title: string; - mount: ManagementSectionMount; - order?: number; -} - -export type RegisterManagementApp = (managementApp: RegisterManagementAppArgs) => ManagementApp; - export type Unmount = () => Promise | void; +export type Mount = (params: ManagementAppMountParams) => Unmount | Promise; export interface ManagementAppMountParams { basePath: string; // base path for setting up your router element: HTMLElement; // element the section should render into setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; + history: ScopedHistory; } -export type ManagementSectionMount = ( - params: ManagementAppMountParams -) => Unmount | Promise; - -export interface CreateManagementApp { +export interface CreateManagementItemArgs { id: string; - title: string; - basePath: string; + title: string | ReactElement; order?: number; - mount: ManagementSectionMount; -} - -export interface LegacySection extends LegacyApp { - visibleItems: LegacyApp[]; -} - -export interface LegacyApp { - disabled: boolean; - visible: boolean; - id: string; - display: string; - url?: string; - euiIconType?: IconType; - icon?: string; - order: number; + euiIconType?: string; // takes precedence over `icon` property. + icon?: string; // URL to image file; fallback if no `euiIconType` } diff --git a/src/legacy/ui/public/management/breadcrumbs.ts b/src/plugins/management/public/utils/breadcrumbs.ts similarity index 85% rename from src/legacy/ui/public/management/breadcrumbs.ts rename to src/plugins/management/public/utils/breadcrumbs.ts index 936e99caff565..147d157d29d7f 100644 --- a/src/legacy/ui/public/management/breadcrumbs.ts +++ b/src/plugins/management/public/utils/breadcrumbs.ts @@ -19,9 +19,9 @@ import { i18n } from '@kbn/i18n'; -export const MANAGEMENT_BREADCRUMB = Object.freeze({ - text: i18n.translate('common.ui.stackManagement.breadcrumb', { +export const MANAGEMENT_BREADCRUMB = { + text: i18n.translate('management.breadcrumb', { defaultMessage: 'Stack Management', }), - href: '#/management', -}); + href: '/', +}; diff --git a/src/legacy/ui/public/management/index.js b/src/plugins/management/public/utils/index.ts similarity index 83% rename from src/legacy/ui/public/management/index.js rename to src/plugins/management/public/utils/index.ts index 25d3678c5dbba..04c0c4c6811c7 100644 --- a/src/legacy/ui/public/management/index.js +++ b/src/plugins/management/public/utils/index.ts @@ -18,5 +18,5 @@ */ export { MANAGEMENT_BREADCRUMB } from './breadcrumbs'; -import { npStart } from 'ui/new_platform'; -export const management = npStart.plugins.management.legacy; +export { ManagementApp, RegisterManagementAppArgs } from './management_app'; +export { ManagementSection, RegisterManagementSectionArgs } from './management_section'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/types.ts b/src/plugins/management/public/utils/management_app.ts similarity index 62% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/types.ts rename to src/plugins/management/public/utils/management_app.ts index 81184d6fdd1a3..a27db5522af82 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/types.ts +++ b/src/plugins/management/public/utils/management_app.ts @@ -17,18 +17,22 @@ * under the License. */ -export interface IndexPatternCreationOption { - text: string; - description?: string; - onClick: () => void; +import { CreateManagementItemArgs, Mount } from '../types'; +import { ManagementItem } from './management_item'; + +export interface RegisterManagementAppArgs extends CreateManagementItemArgs { + mount: Mount; + basePath: string; } -export interface IndexPattern { - id: string; - title: string; - url: string; - active: boolean; - default: boolean; - tag?: string[]; - sort: string; +export class ManagementApp extends ManagementItem { + public readonly mount: Mount; + public readonly basePath: string; + + constructor(args: RegisterManagementAppArgs) { + super(args); + + this.mount = args.mount; + this.basePath = args.basePath; + } } diff --git a/src/plugins/management/public/utils/management_item.ts b/src/plugins/management/public/utils/management_item.ts new file mode 100644 index 0000000000000..ef0c8e4693895 --- /dev/null +++ b/src/plugins/management/public/utils/management_item.ts @@ -0,0 +1,46 @@ +/* + * 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 { ReactElement } from 'react'; +import { CreateManagementItemArgs } from '../types'; + +export class ManagementItem { + public readonly id: string = ''; + public readonly title: string | ReactElement = ''; + public readonly order: number; + public readonly euiIconType?: string; + public readonly icon?: string; + + public enabled: boolean = true; + + constructor({ id, title, order = 100, euiIconType, icon }: CreateManagementItemArgs) { + this.id = id; + this.title = title; + this.order = order; + this.euiIconType = euiIconType; + this.icon = icon; + } + + disable() { + this.enabled = false; + } + + enable() { + this.enabled = true; + } +} diff --git a/src/plugins/management/public/utils/management_section.test.ts b/src/plugins/management/public/utils/management_section.test.ts new file mode 100644 index 0000000000000..f5ce86f5dc963 --- /dev/null +++ b/src/plugins/management/public/utils/management_section.test.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ManagementSection, RegisterManagementSectionArgs } from './management_section'; + +describe('ManagementSection', () => { + const createSection = ( + config: RegisterManagementSectionArgs = { + id: 'test-section', + title: 'Test Section', + } as RegisterManagementSectionArgs + ) => new ManagementSection(config); + + test('cannot register two apps with the same id', () => { + const section = createSection(); + const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} }; + + section.registerApp(testAppConfig); + + expect(section.apps.length).toEqual(1); + + expect(() => { + section.registerApp(testAppConfig); + }).toThrow(); + }); + + test('can enable and disable apps', () => { + const section = createSection(); + const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} }; + + const app = section.registerApp(testAppConfig); + + expect(section.getAppsEnabled().length).toEqual(1); + + app.disable(); + + expect(section.getAppsEnabled().length).toEqual(0); + }); +}); diff --git a/src/plugins/management/public/utils/management_section.ts b/src/plugins/management/public/utils/management_section.ts new file mode 100644 index 0000000000000..d226825e39d19 --- /dev/null +++ b/src/plugins/management/public/utils/management_section.ts @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Assign } from '@kbn/utility-types'; +import { CreateManagementItemArgs, ManagementSectionId } from '../types'; +import { ManagementItem } from './management_item'; +import { ManagementApp, RegisterManagementAppArgs } from './management_app'; + +export type RegisterManagementSectionArgs = Assign< + CreateManagementItemArgs, + { id: ManagementSectionId | string } +>; + +export class ManagementSection extends ManagementItem { + public readonly apps: ManagementApp[] = []; + + constructor(args: RegisterManagementSectionArgs) { + super(args); + } + + registerApp(args: Omit) { + if (this.getApp(args.id)) { + throw new Error(`Management app already registered - id: ${args.id}, title: ${args.title}`); + } + + const app = new ManagementApp({ + ...args, + basePath: `/${this.id}/${args.id}`, + }); + + this.apps.push(app); + + return app; + } + + getApp(id: ManagementApp['id']) { + return this.apps.find((app) => app.id === id); + } + + getAppsEnabled() { + return this.apps.filter((app) => app.enabled); + } +} diff --git a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx index c1daf3445219f..9cfe99fd3bbf8 100644 --- a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx +++ b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx @@ -19,10 +19,10 @@ import React, { lazy, Suspense } from 'react'; import ReactDOM from 'react-dom'; -import { HashRouter, Switch, Route } from 'react-router-dom'; +import { Router, Switch, Route } from 'react-router-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { EuiLoadingSpinner } from '@elastic/eui'; -import { CoreSetup, Capabilities } from 'src/core/public'; +import { CoreSetup } from 'src/core/public'; import { ManagementAppMountParams } from '../../../management/public'; import { StartDependencies, SavedObjectsManagementPluginStart } from '../plugin'; import { ISavedObjectsManagementServiceRegistry } from '../services'; @@ -44,30 +44,41 @@ export const mountManagementSection = async ({ serviceRegistry, }: MountParams) => { const [coreStart, { data }, pluginStart] = await core.getStartServices(); - const { element, basePath, setBreadcrumbs } = mountParams; + const { element, history, setBreadcrumbs } = mountParams; if (allowedObjectTypes === undefined) { allowedObjectTypes = await getAllowedTypes(coreStart.http); } const capabilities = coreStart.application.capabilities; + const RedirectToHomeIfUnauthorized: React.FunctionComponent = ({ children }) => { + const allowed = capabilities?.management?.kibana?.objects ?? false; + + if (!allowed) { + coreStart.application.navigateToApp('home'); + return null; + } + return children! as React.ReactElement; + }; + ReactDOM.render( - + - + }> - + }> - + , element ); @@ -90,14 +101,3 @@ export const mountManagementSection = async ({ ReactDOM.unmountComponentAtNode(element); }; }; - -const RedirectToHomeIfUnauthorized: React.FunctionComponent<{ - capabilities: Capabilities; -}> = ({ children, capabilities }) => { - const allowed = capabilities?.management?.kibana?.objects ?? false; - if (!allowed) { - window.location.hash = '/home'; - return null; - } - return children! as React.ReactElement; -}; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx index 83644e6404c81..1572ef9164700 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx @@ -26,6 +26,7 @@ import { OverlayStart, NotificationsStart, SimpleSavedObject, + ScopedHistory, } from '../../../../../core/public'; import { ISavedObjectsManagementServiceRegistry } from '../../services'; import { Header, NotFoundErrors, Intro, Form } from './components'; @@ -41,6 +42,7 @@ interface SavedObjectEditionProps { notifications: NotificationsStart; notFoundType?: string; savedObjectsClient: SavedObjectsClientContract; + history: ScopedHistory; } interface SavedObjectEditionState { @@ -171,6 +173,6 @@ export class SavedObjectEdition extends Component< }; redirectToListing() { - window.location.hash = '/management/kibana/objects'; + this.props.history.push('/'); } } diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index 78af61c20c828..26ddb0809d24b 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -314,7 +314,7 @@ exports[`SavedObjectsTable relationships should show the flyout 1`] = ` Object { "id": "2", "meta": Object { - "editUrl": "#/management/kibana/objects/savedSearches/2", + "editUrl": "/management/kibana/objects/savedSearches/2", "icon": "search", "inAppUrl": Object { "path": "/discover/2", @@ -404,7 +404,7 @@ exports[`SavedObjectsTable should render normally 1`] = ` Object { "id": "2", "meta": Object { - "editUrl": "#/management/kibana/objects/savedSearches/2", + "editUrl": "/management/kibana/objects/savedSearches/2", "icon": "search", "inAppUrl": Object { "path": "/discover/2", @@ -417,7 +417,7 @@ exports[`SavedObjectsTable should render normally 1`] = ` Object { "id": "3", "meta": Object { - "editUrl": "#/management/kibana/objects/savedDashboards/3", + "editUrl": "/management/kibana/objects/savedDashboards/3", "icon": "dashboardApp", "inAppUrl": Object { "path": "/dashboard/3", @@ -430,7 +430,7 @@ exports[`SavedObjectsTable should render normally 1`] = ` Object { "id": "4", "meta": Object { - "editUrl": "#/management/kibana/objects/savedVisualizations/4", + "editUrl": "/management/kibana/objects/savedVisualizations/4", "icon": "visualizeApp", "inAppUrl": Object { "path": "/edit/4", diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap index cb2a2dd0e4d1e..6eb9e36394ee3 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap @@ -455,7 +455,7 @@ exports[`Relationships should render searches normally 1`] = ` "editUrl": "/management/kibana/indexPatterns/patterns/1", "icon": "indexPatternApp", "inAppUrl": Object { - "path": "/app/kibana#/management/kibana/indexPatterns/patterns/1", + "path": "/app/management/kibana/indexPatterns/patterns/1", "uiCapabilitiesPath": "management.kibana.index_patterns", }, "title": "My Index Pattern", diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx index 5ee70e73c873b..9277d9b00305b 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx @@ -112,7 +112,7 @@ describe('Relationships', () => { editUrl: '/management/kibana/indexPatterns/patterns/1', icon: 'indexPatternApp', inAppUrl: { - path: '/app/kibana#/management/kibana/indexPatterns/patterns/1', + path: '/app/management/kibana/indexPatterns/patterns/1', uiCapabilitiesPath: 'management.kibana.index_patterns', }, title: 'My Index Pattern', @@ -141,7 +141,7 @@ describe('Relationships', () => { meta: { title: 'MySearch', icon: 'search', - editUrl: '#/management/kibana/objects/savedSearches/1', + editUrl: '/management/kibana/objects/savedSearches/1', inAppUrl: { path: '/discover/1', uiCapabilitiesPath: 'discover.show', @@ -208,7 +208,7 @@ describe('Relationships', () => { meta: { title: 'MyViz', icon: 'visualizeApp', - editUrl: '#/management/kibana/objects/savedVisualizations/1', + editUrl: '/management/kibana/objects/savedVisualizations/1', inAppUrl: { path: '/edit/1', uiCapabilitiesPath: 'visualize.show', @@ -275,7 +275,7 @@ describe('Relationships', () => { meta: { title: 'MyDashboard', icon: 'dashboardApp', - editUrl: '#/management/kibana/objects/savedDashboards/1', + editUrl: '/management/kibana/objects/savedDashboards/1', inAppUrl: { path: '/dashboard/1', uiCapabilitiesPath: 'dashboard.show', @@ -315,7 +315,7 @@ describe('Relationships', () => { meta: { title: 'MyDashboard', icon: 'dashboardApp', - editUrl: '#/management/kibana/objects/savedDashboards/1', + editUrl: '/management/kibana/objects/savedDashboards/1', inAppUrl: { path: '/dashboard/1', uiCapabilitiesPath: 'dashboard.show', diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index b46bc8dd1b4ee..191bde8b192fd 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx @@ -168,7 +168,7 @@ describe('SavedObjectsTable', () => { meta: { title: `MySearch`, icon: 'search', - editUrl: '#/management/kibana/objects/savedSearches/2', + editUrl: '/management/kibana/objects/savedSearches/2', inAppUrl: { path: '/discover/2', uiCapabilitiesPath: 'discover.show', @@ -181,7 +181,7 @@ describe('SavedObjectsTable', () => { meta: { title: `MyDashboard`, icon: 'dashboardApp', - editUrl: '#/management/kibana/objects/savedDashboards/3', + editUrl: '/management/kibana/objects/savedDashboards/3', inAppUrl: { path: '/dashboard/3', uiCapabilitiesPath: 'dashboard.show', @@ -194,7 +194,7 @@ describe('SavedObjectsTable', () => { meta: { title: `MyViz`, icon: 'visualizeApp', - editUrl: '#/management/kibana/objects/savedVisualizations/4', + editUrl: '/management/kibana/objects/savedVisualizations/4', inAppUrl: { path: '/edit/4', uiCapabilitiesPath: 'visualize.show', @@ -441,7 +441,7 @@ describe('SavedObjectsTable', () => { meta: { title: `MySearch`, icon: 'search', - editUrl: '#/management/kibana/objects/savedSearches/2', + editUrl: '/management/kibana/objects/savedSearches/2', inAppUrl: { path: '/discover/2', uiCapabilitiesPath: 'discover.show', @@ -456,7 +456,7 @@ describe('SavedObjectsTable', () => { type: 'search', meta: { title: 'MySearch', - editUrl: '#/management/kibana/objects/savedSearches/2', + editUrl: '/management/kibana/objects/savedSearches/2', icon: 'search', inAppUrl: { path: '/discover/2', diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index d9b856a79b496..c24f5d29f3870 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -457,8 +457,8 @@ export class SavedObjectsTable extends Component void; + history: ScopedHistory; }) => { const { service: serviceName, id } = useParams<{ service: string; id: string }>(); const capabilities = coreStart.application.capabilities; @@ -47,7 +49,7 @@ const SavedObjectsEditionPage = ({ text: i18n.translate('savedObjectsManagement.breadcrumb.index', { defaultMessage: 'Saved objects', }), - href: '#/management/kibana/objects', + href: '/', }, { text: i18n.translate('savedObjectsManagement.breadcrumb.edit', { @@ -68,6 +70,7 @@ const SavedObjectsEditionPage = ({ notifications={coreStart.notifications} capabilities={capabilities} notFoundType={query.notFound as string} + history={history} /> ); }; diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx index 4e8418d7406b5..75692777f08bb 100644 --- a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx @@ -52,7 +52,7 @@ const SavedObjectsTablePage = ({ text: i18n.translate('savedObjectsManagement.breadcrumb.index', { defaultMessage: 'Saved objects', }), - href: '#/management/kibana/objects', + href: '/', }, ]); }, [setBreadcrumbs]); @@ -73,11 +73,7 @@ const SavedObjectsTablePage = ({ goInspectObject={(savedObject) => { const { editUrl } = savedObject.meta; if (editUrl) { - // previously, kbnUrl.change(object.meta.editUrl); was used. - // using direct access to location.hash seems the only option for now, - // as using react-router-dom will prefix the url with the router's basename - // which should be ignored there. - window.location.hash = editUrl; + return coreStart.application.navigateToUrl('/app' + editUrl); } }} canGoInApp={(savedObject) => { diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index 1d765c70edb97..f3d6318db89f2 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -82,7 +82,7 @@ export class SavedObjectsManagementPlugin 'Import, export, and manage your saved searches, visualizations, and dashboards.', }), icon: 'savedObjectsApp', - path: '/app/kibana#/management/kibana/objects', + path: '/app/management/kibana/objects', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/src/plugins/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts index 2e058fdc13e4a..53c79b738f750 100644 --- a/src/plugins/telemetry/common/constants.ts +++ b/src/plugins/telemetry/common/constants.ts @@ -49,7 +49,7 @@ export const LOCALSTORAGE_KEY = 'telemetry.data'; /** * Link to Advanced Settings. */ -export const PATH_TO_ADVANCED_SETTINGS = 'kibana#/management/kibana/settings'; +export const PATH_TO_ADVANCED_SETTINGS = 'management/kibana/settings'; /** * Link to the Elastic Telemetry privacy statement. diff --git a/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap b/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap index 193205cd394e2..dd774d956dc10 100644 --- a/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap +++ b/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap @@ -10,7 +10,7 @@ exports[`OptInDetailsComponent renders as expected 1`] = ` values={ Object { "disableLink": collectionActions.handleAdd(this.props); const handleDelete = collectionActions.handleDelete.bind(null, this.props, model); const { intl } = this.props; const operatorOptions = [ diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js index 6c8fe7ca16619..f8752ce8fa3a8 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js @@ -91,21 +91,29 @@ export function mathAgg(resp, panel, series, meta) { // a safety check for the user const someNull = values(params).some((v) => v == null); if (someNull) return [ts, null]; - // calculate the result based on the user's script and return the value - const result = evaluate(mathMetric.script, { - params: { - ...params, - _index: index, - _timestamp: ts, - _all: all, - _interval: split.meta.bucketSize * 1000, - }, - }); - // if the result is an object (usually when the user is working with maps and functions) flatten the results and return the last value. - if (typeof result === 'object') { - return [ts, last(flatten(result.valueOf()))]; + try { + // calculate the result based on the user's script and return the value + const result = evaluate(mathMetric.script, { + params: { + ...params, + _index: index, + _timestamp: ts, + _all: all, + _interval: split.meta.bucketSize * 1000, + }, + }); + // if the result is an object (usually when the user is working with maps and functions) flatten the results and return the last value. + if (typeof result === 'object') { + return [ts, last(flatten(result.valueOf()))]; + } + return [ts, result]; + } catch (e) { + if (e.message === 'Cannot divide by 0') { + // Drop division by zero errors and treat as null value + return [ts, null]; + } + throw e; } - return [ts, result]; }); return { id: split.id, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js new file mode 100644 index 0000000000000..79cfd2ddd54bb --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js @@ -0,0 +1,148 @@ +/* + * 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 { mathAgg } from './math'; +import { stdMetric } from './std_metric'; + +describe('math(resp, panel, series)', () => { + let panel; + let series; + let resp; + beforeEach(() => { + panel = { + time_field: 'timestamp', + }; + series = { + chart_type: 'line', + stacked: false, + line_width: 1, + point_size: 1, + fill: 0, + id: 'test', + label: 'Math', + split_mode: 'terms', + split_color_mode: 'gradient', + color: '#F00', + metrics: [ + { + id: 'avgcpu', + type: 'avg', + field: 'cpu', + }, + { + id: 'mincpu', + type: 'min', + field: 'cpu', + }, + { + id: 'mathagg', + type: 'math', + script: 'divide(params.a, params.b)', + variables: [ + { name: 'a', field: 'avgcpu' }, + { name: 'b', field: 'mincpu' }, + ], + }, + ], + }; + resp = { + aggregations: { + test: { + meta: { + bucketSize: 5, + }, + buckets: [ + { + key: 'example-01', + timeseries: { + buckets: [ + { + key: 1, + avgcpu: { value: 0.25 }, + mincpu: { value: 0.125 }, + }, + { + key: 2, + avgcpu: { value: 0.25 }, + mincpu: { value: 0.25 }, + }, + ], + }, + }, + ], + }, + }, + }; + }); + + test('calls next when finished', () => { + const next = jest.fn(); + mathAgg(resp, panel, series)(next)([]); + expect(next.mock.calls.length).toEqual(1); + }); + + test('creates a series', () => { + const next = mathAgg(resp, panel, series)((results) => results); + const results = stdMetric(resp, panel, series)(next)([]); + expect(results).toHaveLength(1); + + expect(results[0]).toEqual({ + id: 'test:example-01', + label: 'example-01', + color: 'rgb(255, 0, 0)', + stack: false, + seriesId: 'test', + lines: { show: true, fill: 0, lineWidth: 1, steps: false }, + points: { show: true, radius: 1, lineWidth: 1 }, + bars: { fill: 0, lineWidth: 1, show: false }, + data: [ + [1, 2], + [2, 1], + ], + }); + }); + + test('turns division by zero into null values', () => { + resp.aggregations.test.buckets[0].timeseries.buckets[0].mincpu = 0; + const next = mathAgg(resp, panel, series)((results) => results); + const results = stdMetric(resp, panel, series)(next)([]); + expect(results).toHaveLength(1); + + expect(results[0]).toEqual( + expect.objectContaining({ + data: [ + [1, null], + [2, 1], + ], + }) + ); + }); + + test('throws on actual tinymath expression errors', () => { + series.metrics[2].script = 'notExistingFn(params.a)'; + expect(() => + stdMetric(resp, panel, series)(mathAgg(resp, panel, series)((results) => results))([]) + ).toThrow(); + + series.metrics[2].script = 'divide(params.a, params.b'; + expect(() => + stdMetric(resp, panel, series)(mathAgg(resp, panel, series)((results) => results))([]) + ).toThrow(); + }); +}); diff --git a/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts b/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts index c74d1dd72d761..e8838f57ae365 100644 --- a/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts +++ b/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts @@ -66,6 +66,7 @@ const backgroundColorRulesItems = schema.object({ id: stringOptionalNullable, background_color: stringOptionalNullable, color: stringOptionalNullable, + operator: stringOptionalNullable, }); const gaugeColorRulesItems = schema.object({ @@ -73,7 +74,7 @@ const gaugeColorRulesItems = schema.object({ text: stringOptionalNullable, id: stringOptionalNullable, operator: stringOptionalNullable, - value: schema.number(), + value: schema.maybe(schema.nullable(schema.number())), }); const metricsItems = schema.object({ field: stringOptionalNullable, diff --git a/src/plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js b/src/plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js index 9d8f9dccb1f0a..d2cf81a1410c6 100644 --- a/src/plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js +++ b/src/plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js @@ -19,7 +19,7 @@ import React from 'react'; import _ from 'lodash'; -import numeral from 'numeral'; +import numeral from '@elastic/numeral'; import { renderToStaticMarkup } from 'react-dom/server'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js index 8e3429a39c955..938d3d0ec6d74 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js @@ -20,7 +20,7 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; -import numeral from 'numeral'; +import numeral from '@elastic/numeral'; import { PieContainsAllZeros, ContainerTooSmall } from '../errors'; import { Chart } from './_chart'; import { truncateLabel } from '../components/labels/truncate_labels'; diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts index 83d53d27e41fd..d27d021465dc9 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts @@ -623,12 +623,12 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false }, + params: { hey: 'ya' }, }, ]; const tableDoc = generateDoc('table', aggs); @@ -656,7 +656,7 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', @@ -681,7 +681,7 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, ]; const tableDoc = generateDoc('table', aggs); @@ -701,12 +701,12 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false }, + params: { hey: 'ya' }, }, { id: '4', @@ -731,15 +731,15 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false }, + params: { hey: 'ya' }, }, ]; - const expected = [{}, { foo: 'bar', row: true }, { hey: 'ya' }]; + const expected = [{}, { foo: 'bar' }, { hey: 'ya' }]; const migrated = migrate(generateDoc('table', aggs)); const actual = JSON.parse(migrated.attributes.visState); @@ -1386,11 +1386,11 @@ describe('migration visualization', () => { doc as Parameters[0], savedObjectMigrationContext ); - const generateDoc = (params: any) => ({ + const generateDoc = (visState: any) => ({ attributes: { title: 'My Vis', description: 'This is my super cool vis.', - visState: JSON.stringify({ params }), + visState: JSON.stringify(visState), uiStateJSON: '{}', version: 1, kibanaSavedObjectMeta: { @@ -1416,7 +1416,7 @@ describe('migration visualization', () => { }, ], }; - const timeSeriesDoc = generateDoc(params); + const timeSeriesDoc = generateDoc({ params }); const migratedtimeSeriesDoc = migrate(timeSeriesDoc); const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; @@ -1453,12 +1453,38 @@ describe('migration visualization', () => { }, ], }; - const timeSeriesDoc = generateDoc(params); + const timeSeriesDoc = generateDoc({ params }); const migratedtimeSeriesDoc = migrate(timeSeriesDoc); const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; expect(migratedParams.gauge_color_rules[1]).toEqual(params.gauge_color_rules[1]); }); + + it('should move "row" field on split chart by a row or column to vis.params', () => { + const visData = { + type: 'area', + aggs: [ + { + id: '1', + schema: 'metric', + params: {}, + }, + { + id: '2', + type: 'terms', + schema: 'split', + params: { foo: 'bar', row: true }, + }, + ], + params: {}, + }; + + const migrated = migrate(generateDoc(visData)); + const actual = JSON.parse(migrated.attributes.visState); + + expect(actual.aggs.filter((agg: any) => 'row' in agg.params)).toEqual([]); + expect(actual.params.row).toBeTruthy(); + }); }); describe('7.8.0 tsvb split_color_mode', () => { diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts index 71b286cd91a54..27fe722019a27 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts @@ -131,6 +131,50 @@ const migrateOperatorKeyTypo: SavedObjectMigrationFn = (doc) => { return doc; }; +/** + * Moving setting wether to do a row or column split to vis.params + * + * @see https://github.com/elastic/kibana/pull/58462/files#diff-ae69fe15b20a5099d038e9bbe2ed3849 + **/ +const migrateSplitByChartRow: SavedObjectMigrationFn = (doc) => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState: any; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + if (visState && visState.aggs && visState.params) { + let row: boolean | undefined; + + visState.aggs.forEach((agg: any) => { + if (agg.type === 'terms' && agg.schema === 'split' && 'row' in agg.params) { + row = agg.params.row; + + delete agg.params.row; + } + }); + + if (row !== undefined) { + visState.params.row = row; + } + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } + + return doc; +}; + // Migrate date histogram aggregation (remove customInterval) const migrateDateHistogramAggregation: SavedObjectMigrationFn = (doc) => { const visStateJSON = get(doc, 'attributes.visState'); @@ -673,6 +717,6 @@ export const visualizationSavedObjectTypeMigrations = { ), '7.3.1': flow>(migrateFiltersAggQueryStringQueries), '7.4.2': flow>(transformSplitFiltersStringToQueryObject), - '7.7.0': flow>(migrateOperatorKeyTypo), + '7.7.0': flow>(migrateOperatorKeyTypo, migrateSplitByChartRow), '7.8.0': flow>(migrateTsvbDefaultColorPalettes), }; diff --git a/src/plugins/visualize/public/application/legacy_app.js b/src/plugins/visualize/public/application/legacy_app.js index c9ccd79ce6a8a..24316f373a59e 100644 --- a/src/plugins/visualize/public/application/legacy_app.js +++ b/src/plugins/visualize/public/application/legacy_app.js @@ -211,22 +211,16 @@ export function initVisualizeApp(app, deps) { mapping: { visualization: VisualizeConstants.LANDING_PAGE_PATH, search: { - app: 'kibana', - path: - '#/management/kibana/objects/savedVisualizations/' + - $route.current.params.id, + app: 'management', + path: 'kibana/objects/savedVisualizations/' + $route.current.params.id, }, 'index-pattern': { - app: 'kibana', - path: - '#/management/kibana/objects/savedVisualizations/' + - $route.current.params.id, + app: 'management', + path: 'kibana/objects/savedVisualizations/' + $route.current.params.id, }, 'index-pattern-field': { - app: 'kibana', - path: - '#/management/kibana/objects/savedVisualizations/' + - $route.current.params.id, + app: 'management', + path: 'kibana/objects/savedVisualizations/' + $route.current.params.id, }, }, toastNotifications, diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts index 8a104dc51feb4..8a05adc18964a 100644 --- a/src/plugins/visualize/public/plugin.ts +++ b/src/plugins/visualize/public/plugin.ts @@ -102,7 +102,7 @@ export class VisualizePlugin core.application.register({ id: 'visualize', title: 'Visualize', - order: -1002, + order: 8000, euiIconType: 'visualizeApp', defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index 115399cb57dfa..e15a9e989d21f 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -285,7 +285,7 @@ export default function ({ getService }: FtrProviderContext) { '/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357', inAppUrl: { path: - '/app/kibana#/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357', + '/app/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'management.kibana.index_patterns', }, }); diff --git a/test/api_integration/apis/saved_objects_management/relationships.ts b/test/api_integration/apis/saved_objects_management/relationships.ts index bbc691dbc6398..1db4df181e0e9 100644 --- a/test/api_integration/apis/saved_objects_management/relationships.ts +++ b/test/api_integration/apis/saved_objects_management/relationships.ts @@ -86,7 +86,7 @@ export default function ({ getService }: FtrProviderContext) { '/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357', inAppUrl: { path: - '/app/kibana#/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357', + '/app/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'management.kibana.index_patterns', }, }, @@ -127,7 +127,7 @@ export default function ({ getService }: FtrProviderContext) { '/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357', inAppUrl: { path: - '/app/kibana#/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357', + '/app/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'management.kibana.index_patterns', }, }, diff --git a/test/common/services/security/test_user.ts b/test/common/services/security/test_user.ts index e72ded3a6f2fd..c0abf236e3412 100644 --- a/test/common/services/security/test_user.ts +++ b/test/common/services/security/test_user.ts @@ -71,7 +71,7 @@ export async function createTestUserService( } } - async setRoles(roles: string[]) { + async setRoles(roles: string[], shouldRefreshBrowser: boolean = true) { if (isEnabled()) { log.debug(`set roles = ${roles}`); await user.create('test_user', { @@ -80,7 +80,7 @@ export async function createTestUserService( full_name: 'test user', }); - if (browser && testSubjects) { + if (browser && testSubjects && shouldRefreshBrowser) { if (await testSubjects.exists('kibanaChrome', { allowHidden: true })) { await browser.refresh(); await testSubjects.find('kibanaChrome', config.get('timeouts.find') * 10); diff --git a/test/functional/apps/dashboard/dashboard_filter_bar.js b/test/functional/apps/dashboard/dashboard_filter_bar.js index 417b6fb066172..6bc34a8b998a4 100644 --- a/test/functional/apps/dashboard/dashboard_filter_bar.js +++ b/test/functional/apps/dashboard/dashboard_filter_bar.js @@ -186,5 +186,37 @@ export default function ({ getService, getPageObjects }) { expect(filterCount).to.equal(1); }); }); + + describe('bad filters are loaded properly', function () { + before(async () => { + await filterBar.ensureFieldEditorModalIsClosed(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.loadSavedDashboard('dashboard with bad filters'); + }); + + it('filter with non-existent index pattern renders in error mode', async function () { + const hasBadFieldFilter = await filterBar.hasFilter('name', 'error', false); + expect(hasBadFieldFilter).to.be(true); + }); + + it('filter with non-existent field renders in error mode', async function () { + const hasBadFieldFilter = await filterBar.hasFilter('baad-field', 'error', false); + expect(hasBadFieldFilter).to.be(true); + }); + + it('filter from unrelated index pattern is still applicable if field name is found', async function () { + const hasUnrelatedIndexPatternFilterPhrase = await filterBar.hasFilter( + '@timestamp', + '123', + true + ); + expect(hasUnrelatedIndexPatternFilterPhrase).to.be(true); + }); + + it('filter from unrelated index pattern is rendred as a warning if field name is not found', async function () { + const hasWarningFieldFilter = await filterBar.hasFilter('extension', 'warn', true); + expect(hasWarningFieldFilter).to.be(true); + }); + }); }); } diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 163022e32c82f..9f3ce667d64b5 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -233,5 +233,19 @@ export default function ({ getService, getPageObjects }) { expect(await PageObjects.discover.getNrOfFetches()).to.be(1); }); }); + + describe('empty query', function () { + it('should update the histogram timerange when the query is resubmitted', async function () { + await kibanaServer.uiSettings.update({ + 'timepicker:timeDefaults': '{ "from": "2015-09-18T19:37:13.000Z", "to": "now"}', + }); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.awaitKibanaChrome(); + const initialTimeString = await PageObjects.discover.getChartTimespan(); + await queryBar.submitQuery(); + const refreshedTimeString = await PageObjects.discover.getChartTimespan(); + expect(refreshedTimeString).not.to.be(initialTimeString); + }); + }); }); } diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index 06b2e86dd1af9..6e4b820879ed3 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -82,9 +82,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { let objects = await PageObjects.settings.getSavedObjectsInTable(); expect(objects.includes('A Dashboard')).to.be(true); - await PageObjects.common.navigateToActualUrl( - 'kibana', - '/management/kibana/objects/savedDashboards/i-exist' + await PageObjects.common.navigateToUrl( + 'management', + 'kibana/objects/savedDashboards/i-exist', + { + shouldUseHashForSubUrl: false, + } ); await testSubjects.existOrFail('savedObjectEditSave'); @@ -100,9 +103,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(objects.includes('A Dashboard')).to.be(false); expect(objects.includes('Edited Dashboard')).to.be(true); - await PageObjects.common.navigateToActualUrl( - 'kibana', - '/management/kibana/objects/savedDashboards/i-exist' + await PageObjects.common.navigateToUrl( + 'management', + 'kibana/objects/savedDashboards/i-exist', + { + shouldUseHashForSubUrl: false, + } ); expect(await getFieldValue('title')).to.eql('Edited Dashboard'); @@ -110,9 +116,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('allows to delete a saved object', async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - '/management/kibana/objects/savedDashboards/i-exist' + await PageObjects.common.navigateToUrl( + 'management', + 'kibana/objects/savedDashboards/i-exist', + { + shouldUseHashForSubUrl: false, + } ); await focusAndClickButton('savedObjectEditDelete'); @@ -124,7 +133,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('preserves the object references when saving', async () => { const testVisualizationUrl = - '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed'; + 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed'; const visualizationRefs = [ { name: 'kibanaSavedObjectMeta.searchSourceJSON.index', @@ -139,7 +148,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const objects = await PageObjects.settings.getSavedObjectsInTable(); expect(objects.includes('A Pie')).to.be(true); - await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl); + await PageObjects.common.navigateToUrl('management', testVisualizationUrl, { + shouldUseHashForSubUrl: false, + }); await testSubjects.existOrFail('savedObjectEditSave'); @@ -151,7 +162,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.settings.getSavedObjectsInTable(); - await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl); + await PageObjects.common.navigateToUrl('management', testVisualizationUrl, { + shouldUseHashForSubUrl: false, + }); // Parsing to avoid random keys ordering issues in raw string comparison expect(JSON.parse(await getAceEditorFieldValue('references'))).to.eql(visualizationRefs); @@ -162,7 +175,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.settings.getSavedObjectsInTable(); - await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl); + await PageObjects.common.navigateToUrl('management', testVisualizationUrl, { + shouldUseHashForSubUrl: false, + }); displayedReferencesValue = await getAceEditorFieldValue('references'); diff --git a/test/functional/config.js b/test/functional/config.js index 52806e8ca68ea..c377e64f2fc45 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -83,9 +83,12 @@ export default async function ({ readConfigFile }) { pathname: '/app/dashboards', hash: '/list', }, + management: { + pathname: '/app/management', + }, + /** @obsolete "management" should be instead of "settings" **/ settings: { - pathname: '/app/kibana', - hash: '/management', + pathname: '/app/management', }, timelion: { pathname: '/app/timelion', diff --git a/test/functional/fixtures/es_archiver/README.md b/test/functional/fixtures/es_archiver/README.md new file mode 100644 index 0000000000000..ca0f612fad06b --- /dev/null +++ b/test/functional/fixtures/es_archiver/README.md @@ -0,0 +1,10 @@ +## Changing test data sets + +If you need to update these datasets use: + + * Run the dev server `node scripts/functional_tests_server.js` + * When it starts, use `es_archiver` to load the dataset you want to change + * Make the changes you want + * Export the data by running `node scripts/es_archiver.js save data .kibana` + * Move the mapping and data files to the correct location and commit your changes + diff --git a/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz b/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz index e83e34a2e07fa..a052aad9450f5 100644 Binary files a/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz and b/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json b/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json index 7f4cdd2906d44..9f5edaad0fe76 100644 --- a/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json +++ b/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json @@ -1,13 +1,74 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "config": "ae24d22d5986d04124cc6568f771066f", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "url": "b675c3be8d76ecf029294d51dc7ec65d", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, "dynamic": "strict", "properties": { + "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" + } + } + }, "config": { "dynamic": "true", "properties": { + "accessibility:disableAnimations": { + "type": "boolean" + }, "buildNum": { "type": "keyword" }, @@ -40,6 +101,9 @@ }, "notifications:lifetime:warning": { "type": "long" + }, + "xPackMonitoring:showBanner": { + "type": "boolean" } } }, @@ -92,9 +156,6 @@ "title": { "type": "text" }, - "uiStateJSON": { - "type": "text" - }, "version": { "type": "integer" } @@ -122,6 +183,122 @@ }, "title": { "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "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" } } }, @@ -161,6 +338,34 @@ } } }, + "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": { @@ -202,9 +407,23 @@ } } }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, "type": { "type": "keyword" }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, "updated_at": { "type": "date" }, @@ -222,7 +441,6 @@ "url": { "fields": { "keyword": { - "ignore_above": 2048, "type": "keyword" } }, @@ -242,7 +460,7 @@ } } }, - "savedSearchId": { + "savedSearchRefName": { "type": "keyword" }, "title": { diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 7810cce5c78bb..91e9c020a0e7c 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -147,13 +147,19 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo shouldLoginIfPrompted = true, useActualUrl = false, insertTimestamp = true, + shouldUseHashForSubUrl = true, } = {} ) { - const appConfig = { + const appConfig: { pathname: string; hash?: string } = { pathname: `${basePath}${config.get(['apps', appName]).pathname}`, - hash: useActualUrl ? subUrl : `/${appName}/${subUrl}`, }; + if (shouldUseHashForSubUrl) { + appConfig.hash = useActualUrl ? subUrl : `/${appName}/${subUrl}`; + } else { + appConfig.pathname += `/${subUrl}`; + } + await this.navigate({ appConfig, ensureCurrentUrl, diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 9cf7a48cb3d88..f5b4eb7ad5de8 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -300,7 +300,9 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider async getIndexPatternList() { await testSubjects.existOrFail('indexPatternTable', { timeout: 5000 }); - return await find.allByCssSelector('[data-test-subj="indexPatternTable"] .euiTable a'); + return await find.allByCssSelector( + '[data-test-subj="indexPatternTable"] .euiTable .euiTableRow' + ); } async isIndexPatternListEmpty() { diff --git a/test/functional/services/dashboard/add_panel.js b/test/functional/services/dashboard/add_panel.ts similarity index 88% rename from test/functional/services/dashboard/add_panel.js rename to test/functional/services/dashboard/add_panel.ts index 6259203982161..1263501aa9c13 100644 --- a/test/functional/services/dashboard/add_panel.js +++ b/test/functional/services/dashboard/add_panel.ts @@ -17,7 +17,9 @@ * under the License. */ -export function DashboardAddPanelProvider({ getService, getPageObjects }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function DashboardAddPanelProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -39,7 +41,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { await PageObjects.common.sleep(500); } - async clickAddNewEmbeddableLink(type) { + async clickAddNewEmbeddableLink(type: string) { await testSubjects.click('createNew'); await testSubjects.click(`createNew-${type}`); await testSubjects.missingOrFail(`createNew-${type}`); @@ -50,7 +52,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { await testSubjects.click('savedObjectFinderFilterButton'); } - async toggleFilter(type) { + async toggleFilter(type: string) { log.debug(`DashboardAddPanel.addToFilter(${type})`); await this.waitForListLoading(); await this.toggleFilterPopover(); @@ -61,7 +63,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { async addEveryEmbeddableOnCurrentPage() { log.debug('addEveryEmbeddableOnCurrentPage'); const itemList = await testSubjects.find('savedObjectFinderItemList'); - const embeddableList = []; + const embeddableList: string[] = []; await retry.try(async () => { const embeddableRows = await itemList.findAllByCssSelector('li'); for (let i = 0; i < embeddableRows.length; i++) { @@ -130,7 +132,7 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { await flyout.ensureClosed('dashboardAddPanel'); } - async addEveryVisualization(filter) { + async addEveryVisualization(filter: string) { log.debug('DashboardAddPanel.addEveryVisualization'); await this.ensureAddPanelIsShowing(); await this.toggleFilter('visualization'); @@ -138,16 +140,16 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { await this.filterEmbeddableNames(filter.replace('-', ' ')); } let morePages = true; - const vizList = []; + const vizList: string[][] = []; while (morePages) { vizList.push(await this.addEveryEmbeddableOnCurrentPage()); morePages = await this.clickPagerNextButton(); } await this.closeAddPanel(); - return vizList.reduce((acc, vizList) => [...acc, ...vizList], []); + return vizList.reduce((acc, list) => [...acc, ...list], []); } - async addEverySavedSearch(filter) { + async addEverySavedSearch(filter: string) { log.debug('DashboardAddPanel.addEverySavedSearch'); await this.ensureAddPanelIsShowing(); await this.toggleFilter('search'); @@ -161,20 +163,20 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { morePages = await this.clickPagerNextButton(); } await this.closeAddPanel(); - return searchList.reduce((acc, searchList) => [...acc, ...searchList], []); + return searchList.reduce((acc, list) => [...acc, ...list], []); } - async addSavedSearch(searchName) { + async addSavedSearch(searchName: string) { return this.addEmbeddable(searchName, 'search'); } - async addSavedSearches(searches) { + async addSavedSearches(searches: string[]) { for (const name of searches) { await this.addSavedSearch(name); } } - async addVisualizations(visualizations) { + async addVisualizations(visualizations: string[]) { log.debug('DashboardAddPanel.addVisualizations'); const vizList = []; for (const vizName of visualizations) { @@ -184,11 +186,11 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { return vizList; } - async addVisualization(vizName) { + async addVisualization(vizName: string) { return this.addEmbeddable(vizName, 'visualization'); } - async addEmbeddable(embeddableName, embeddableType) { + async addEmbeddable(embeddableName: string, embeddableType: string) { log.debug( `DashboardAddPanel.addEmbeddable, name: ${embeddableName}, type: ${embeddableType}` ); @@ -201,14 +203,14 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { return embeddableName; } - async filterEmbeddableNames(name) { + async filterEmbeddableNames(name: string) { // The search input field may be disabled while the table is loading so wait for it await this.waitForListLoading(); await testSubjects.setValue('savedObjectFinderSearchInput', name); await this.waitForListLoading(); } - async panelAddLinkExists(name) { + async panelAddLinkExists(name: string) { log.debug(`DashboardAddPanel.panelAddLinkExists(${name})`); await this.ensureAddPanelIsShowing(); await this.filterEmbeddableNames(`"${name}"`); diff --git a/test/functional/services/dashboard/expectations.js b/test/functional/services/dashboard/expectations.ts similarity index 82% rename from test/functional/services/dashboard/expectations.js rename to test/functional/services/dashboard/expectations.ts index 66073e1043b0d..77a441a772d84 100644 --- a/test/functional/services/dashboard/expectations.js +++ b/test/functional/services/dashboard/expectations.ts @@ -18,8 +18,10 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../lib/web_element_wrapper'; -export function DashboardExpectProvider({ getService, getPageObjects }) { +export function DashboardExpectProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -29,7 +31,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { const findTimeout = 2500; return new (class DashboardExpect { - async panelCount(expectedCount) { + async panelCount(expectedCount: number) { log.debug(`DashboardExpect.panelCount(${expectedCount})`); await retry.try(async () => { const panelCount = await PageObjects.dashboard.getPanelCount(); @@ -37,7 +39,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async visualizationsArePresent(vizList) { + async visualizationsArePresent(vizList: string[]) { log.debug('Checking all visualisations are present on dashsboard'); let notLoaded = await PageObjects.dashboard.getNotLoadedVisualizations(vizList); // TODO: Determine issue occasionally preventing 'geo map' from loading @@ -45,7 +47,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { expect(notLoaded).to.be.empty(); } - async selectedLegendColorCount(color, expectedCount) { + async selectedLegendColorCount(color: string, expectedCount: number) { log.debug(`DashboardExpect.selectedLegendColorCount(${color}, ${expectedCount})`); await retry.try(async () => { const selectedLegendColor = await testSubjects.findAll( @@ -56,7 +58,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async docTableFieldCount(expectedCount) { + async docTableFieldCount(expectedCount: number) { log.debug(`DashboardExpect.docTableFieldCount(${expectedCount})`); await retry.try(async () => { const docTableCells = await testSubjects.findAll('docTableField', findTimeout); @@ -64,7 +66,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async fieldSuggestions(expectedFields) { + async fieldSuggestions(expectedFields: string[]) { log.debug(`DashboardExpect.fieldSuggestions(${expectedFields})`); const fields = await filterBar.getFilterEditorFields(); expectedFields.forEach((expectedField) => { @@ -72,7 +74,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async legendValuesToExist(legendValues) { + async legendValuesToExist(legendValues: string[]) { log.debug(`DashboardExpect.legendValuesToExist(${legendValues})`); await Promise.all( legendValues.map(async (legend) => { @@ -84,11 +86,11 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { ); } - async textWithinElementsExists(texts, getElementsFn) { + async textWithinElementsExists(texts: string[], getElementsFn: Function) { log.debug(`DashboardExpect.textWithinElementsExists(${texts})`); await retry.try(async () => { - const elements = await getElementsFn(); - const elementTexts = []; + const elements: WebElementWrapper[] = await getElementsFn(); + const elementTexts: string[] = []; await Promise.all( elements.map(async (element) => { elementTexts.push(await element.getVisibleText()); @@ -103,23 +105,23 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async textWithinTestSubjectsExists(texts, selector) { + async textWithinTestSubjectsExists(texts: string[], selector: string) { log.debug(`DashboardExpect.textWithinTestSubjectsExists(${texts})`); log.debug(`textWithinTestSubjectsExists:(${JSON.stringify(texts)},${selector})`); await this.textWithinElementsExists(texts, async () => await testSubjects.findAll(selector)); } - async textWithinCssElementExists(texts, selector) { + async textWithinCssElementExists(texts: string[], selector: string) { log.debug(`DashboardExpect.textWithinCssElementExists(${texts})`); log.debug(`textWithinCssElementExists:(${JSON.stringify(texts)},${selector})`); await this.textWithinElementsExists(texts, async () => await find.allByCssSelector(selector)); } - async textWithinElementsDoNotExist(texts, getElementsFn) { + async textWithinElementsDoNotExist(texts: string[], getElementsFn: Function) { log.debug(`DashboardExpect.textWithinElementsDoNotExist(${texts})`); await retry.try(async () => { - const elements = await getElementsFn(); - const elementTexts = []; + const elements: WebElementWrapper[] = await getElementsFn(); + const elementTexts: string[] = []; await Promise.all( elements.map(async (element) => { elementTexts.push(await element.getVisibleText()); @@ -133,7 +135,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async textWithinCssElementDoNotExist(texts, selector) { + async textWithinCssElementDoNotExist(texts: string[], selector: string) { log.debug(`textWithinCssElementExists:(${JSON.stringify(texts)},${selector})`); await this.textWithinElementsDoNotExist( texts, @@ -141,7 +143,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { ); } - async timelionLegendCount(expectedCount) { + async timelionLegendCount(expectedCount: number) { log.debug(`DashboardExpect.timelionLegendCount(${expectedCount})`); await retry.try(async () => { const flotLegendLabels = await testSubjects.findAll('flotLegendLabel', findTimeout); @@ -160,7 +162,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { expect(tagCloudsHaveContent.indexOf(false)).to.be.greaterThan(-1); } - async tagCloudWithValuesFound(values) { + async tagCloudWithValuesFound(values: string[]) { log.debug(`DashboardExpect.tagCloudWithValuesFound(${values})`); const tagCloudVisualizations = await testSubjects.findAll('tagCloudVisualization'); const matches = await Promise.all( @@ -177,47 +179,47 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { expect(matches.indexOf(true)).to.be.greaterThan(-1); } - async goalAndGuageLabelsExist(labels) { + async goalAndGuageLabelsExist(labels: string[]) { log.debug(`DashboardExpect.goalAndGuageLabelsExist(${labels})`); await this.textWithinCssElementExists(labels, '.chart-label'); } - async metricValuesExist(values) { + async metricValuesExist(values: string[]) { log.debug(`DashboardExpect.metricValuesExist(${values})`); await this.textWithinCssElementExists(values, '.mtrVis__value'); } - async tsvbMetricValuesExist(values) { + async tsvbMetricValuesExist(values: string[]) { log.debug(`DashboardExpect.tsvbMetricValuesExist(${values})`); await this.textWithinTestSubjectsExists(values, 'tsvbMetricValue'); } - async tsvbTopNValuesExist(values) { + async tsvbTopNValuesExist(values: string[]) { log.debug(`DashboardExpect.tsvbTopNValuesExist(${values})`); await this.textWithinTestSubjectsExists(values, 'tsvbTopNValue'); } - async vegaTextsExist(values) { + async vegaTextsExist(values: string[]) { log.debug(`DashboardExpect.vegaTextsExist(${values})`); await this.textWithinCssElementExists(values, '.vgaVis__view text'); } - async vegaTextsDoNotExist(values) { + async vegaTextsDoNotExist(values: string[]) { log.debug(`DashboardExpect.vegaTextsDoNotExist(${values})`); await this.textWithinCssElementDoNotExist(values, '.vgaVis__view text'); } - async tsvbMarkdownWithValuesExists(values) { + async tsvbMarkdownWithValuesExists(values: string[]) { log.debug(`DashboardExpect.tsvbMarkdownWithValuesExists(${values})`); await this.textWithinTestSubjectsExists(values, 'tsvbMarkdown'); } - async markdownWithValuesExists(values) { + async markdownWithValuesExists(values: string[]) { log.debug(`DashboardExpect.markdownWithValuesExists(${values})`); await this.textWithinTestSubjectsExists(values, 'markdownBody'); } - async savedSearchRowCount(expectedCount) { + async savedSearchRowCount(expectedCount: number) { log.debug(`DashboardExpect.savedSearchRowCount(${expectedCount})`); await retry.try(async () => { const savedSearchRows = await testSubjects.findAll( @@ -228,7 +230,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async dataTableRowCount(expectedCount) { + async dataTableRowCount(expectedCount: number) { log.debug(`DashboardExpect.dataTableRowCount(${expectedCount})`); await retry.try(async () => { const dataTableRows = await find.allByCssSelector( @@ -239,7 +241,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async seriesElementCount(expectedCount) { + async seriesElementCount(expectedCount: number) { log.debug(`DashboardExpect.seriesElementCount(${expectedCount})`); await retry.try(async () => { const seriesElements = await find.allByCssSelector('.series', findTimeout); @@ -247,7 +249,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async inputControlItemCount(expectedCount) { + async inputControlItemCount(expectedCount: number) { log.debug(`DashboardExpect.inputControlItemCount(${expectedCount})`); await retry.try(async () => { const inputControlItems = await testSubjects.findAll('inputControlItem'); @@ -255,7 +257,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async lineChartPointsCount(expectedCount) { + async lineChartPointsCount(expectedCount: number) { log.debug(`DashboardExpect.lineChartPointsCount(${expectedCount})`); await retry.try(async () => { const points = await find.allByCssSelector('.points', findTimeout); @@ -263,7 +265,7 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } - async tsvbTableCellCount(expectedCount) { + async tsvbTableCellCount(expectedCount: number) { log.debug(`DashboardExpect.tsvbTableCellCount(${expectedCount})`); await retry.try(async () => { const tableCells = await testSubjects.findAll('tvbTableVis__value', findTimeout); diff --git a/test/functional/services/dashboard/index.js b/test/functional/services/dashboard/index.ts similarity index 100% rename from test/functional/services/dashboard/index.js rename to test/functional/services/dashboard/index.ts diff --git a/test/functional/services/dashboard/panel_actions.js b/test/functional/services/dashboard/panel_actions.ts similarity index 80% rename from test/functional/services/dashboard/panel_actions.js rename to test/functional/services/dashboard/panel_actions.ts index b155d747f3b93..c9a5dcfba32b1 100644 --- a/test/functional/services/dashboard/panel_actions.js +++ b/test/functional/services/dashboard/panel_actions.ts @@ -17,6 +17,9 @@ * under the License. */ +import { FtrProviderContext } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../lib/web_element_wrapper'; + const REMOVE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-deletePanel'; const EDIT_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-editPanel'; const REPLACE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-replacePanel'; @@ -26,13 +29,13 @@ const CUSTOMIZE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-ACTION_CUSTOMIZE_P const OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ = 'embeddablePanelToggleMenuIcon'; const OPEN_INSPECTOR_TEST_SUBJ = 'embeddablePanelAction-openInspector'; -export function DashboardPanelActionsProvider({ getService, getPageObjects }) { +export function DashboardPanelActionsProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['header', 'common']); return new (class DashboardPanelActions { - async findContextMenu(parent) { + async findContextMenu(parent?: WebElementWrapper) { return parent ? await testSubjects.findDescendant(OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ, parent) : await testSubjects.find(OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ); @@ -43,7 +46,7 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { return await testSubjects.exists(OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ); } - async toggleContextMenu(parent) { + async toggleContextMenu(parent?: WebElementWrapper) { log.debug('toggleContextMenu'); await (parent ? parent.moveMouseTo() : testSubjects.moveMouseTo('dashboardPanelTitle')); const toggleMenuItem = await this.findContextMenu(parent); @@ -54,7 +57,7 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.existOrFail('embeddablePanelContextMenuOpen'); } - async openContextMenu(parent) { + async openContextMenu(parent?: WebElementWrapper) { log.debug(`openContextMenu(${parent}`); await this.toggleContextMenu(parent); await this.expectContextMenuToBeOpen(); @@ -77,43 +80,45 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.click(REMOVE_PANEL_DATA_TEST_SUBJ); } - async removePanelByTitle(title) { + async removePanelByTitle(title: string) { const header = await this.getPanelHeading(title); await this.openContextMenu(header); await testSubjects.click(REMOVE_PANEL_DATA_TEST_SUBJ); } - async customizePanel(parent) { + async customizePanel(parent?: WebElementWrapper) { await this.openContextMenu(parent); await testSubjects.click(CUSTOMIZE_PANEL_DATA_TEST_SUBJ); } - async replacePanelByTitle(title) { + async replacePanelByTitle(title?: string) { log.debug(`replacePanel(${title})`); - let panelOptions = null; if (title) { - panelOptions = await this.getPanelHeading(title); + const panelOptions = await this.getPanelHeading(title); + await this.openContextMenu(panelOptions); + } else { + await this.openContextMenu(); } - await this.openContextMenu(panelOptions); await testSubjects.click(REPLACE_PANEL_DATA_TEST_SUBJ); } - async clonePanelByTitle(title) { + async clonePanelByTitle(title?: string) { log.debug(`clonePanel(${title})`); - let panelOptions = null; if (title) { - panelOptions = await this.getPanelHeading(title); + const panelOptions = await this.getPanelHeading(title); + await this.openContextMenu(panelOptions); + } else { + await this.openContextMenu(); } - await this.openContextMenu(panelOptions); await testSubjects.click(CLONE_PANEL_DATA_TEST_SUBJ); } - async openInspectorByTitle(title) { + async openInspectorByTitle(title: string) { const header = await this.getPanelHeading(title); await this.openInspector(header); } - async openInspector(parent) { + async openInspector(parent: WebElementWrapper) { await this.openContextMenu(parent); await testSubjects.click(OPEN_INSPECTOR_TEST_SUBJ); } @@ -163,7 +168,7 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.existOrFail(TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ); } - async getPanelHeading(title) { + async getPanelHeading(title: string) { return await testSubjects.find(`embeddablePanelHeading-${title.replace(/\s/g, '')}`); } @@ -171,13 +176,14 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.click('customizePanelHideTitle'); } - async toggleHidePanelTitle(originalTitle) { + async toggleHidePanelTitle(originalTitle: string) { log.debug(`hidePanelTitle(${originalTitle})`); - let panelOptions = null; if (originalTitle) { - panelOptions = await this.getPanelHeading(originalTitle); + const panelOptions = await this.getPanelHeading(originalTitle); + await this.customizePanel(panelOptions); + } else { + await this.customizePanel(); } - await this.customizePanel(panelOptions); await this.clickHidePanelTitleToggle(); await testSubjects.click('saveNewTitleButton'); } @@ -188,18 +194,19 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { * @param originalTitle - optional to specify which panel to change the title on. * @return {Promise} */ - async setCustomPanelTitle(customTitle, originalTitle) { + async setCustomPanelTitle(customTitle: string, originalTitle?: string) { log.debug(`setCustomPanelTitle(${customTitle}, ${originalTitle})`); - let panelOptions = null; if (originalTitle) { - panelOptions = await this.getPanelHeading(originalTitle); + const panelOptions = await this.getPanelHeading(originalTitle); + await this.customizePanel(panelOptions); + } else { + await this.customizePanel(); } - await this.customizePanel(panelOptions); await testSubjects.setValue('customEmbeddablePanelTitleInput', customTitle); await testSubjects.click('saveNewTitleButton'); } - async resetCustomPanelTitle(panel) { + async resetCustomPanelTitle(panel: WebElementWrapper) { log.debug('resetCustomPanelTitle'); await this.customizePanel(panel); await testSubjects.click('resetCustomEmbeddablePanelTitle'); diff --git a/test/functional/services/dashboard/replace_panel.js b/test/functional/services/dashboard/replace_panel.ts similarity index 86% rename from test/functional/services/dashboard/replace_panel.js rename to test/functional/services/dashboard/replace_panel.ts index faa4d404442d7..d1cb4e5e697a1 100644 --- a/test/functional/services/dashboard/replace_panel.js +++ b/test/functional/services/dashboard/replace_panel.ts @@ -17,7 +17,9 @@ * under the License. */ -export function DashboardReplacePanelProvider({ getService }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function DashboardReplacePanelProvider({ getService }: FtrProviderContext) { const log = getService('log'); const testSubjects = getService('testSubjects'); const flyout = getService('flyout'); @@ -28,7 +30,7 @@ export function DashboardReplacePanelProvider({ getService }) { await testSubjects.click('savedObjectFinderFilterButton'); } - async toggleFilter(type) { + async toggleFilter(type: string) { log.debug(`DashboardReplacePanel.replaceToFilter(${type})`); await this.waitForListLoading(); await this.toggleFilterPopover(); @@ -57,21 +59,21 @@ export function DashboardReplacePanelProvider({ getService }) { await flyout.ensureClosed('dashboardReplacePanel'); } - async replaceSavedSearch(searchName) { + async replaceSavedSearch(searchName: string) { return this.replaceEmbeddable(searchName, 'search'); } - async replaceSavedSearches(searches) { + async replaceSavedSearches(searches: string[]) { for (const name of searches) { await this.replaceSavedSearch(name); } } - async replaceVisualization(vizName) { + async replaceVisualization(vizName: string) { return this.replaceEmbeddable(vizName, 'visualization'); } - async replaceEmbeddable(embeddableName, embeddableType) { + async replaceEmbeddable(embeddableName: string, embeddableType: string) { log.debug( `DashboardReplacePanel.replaceEmbeddable, name: ${embeddableName}, type: ${embeddableType}` ); @@ -86,14 +88,14 @@ export function DashboardReplacePanelProvider({ getService }) { return embeddableName; } - async filterEmbeddableNames(name) { + async filterEmbeddableNames(name: string) { // The search input field may be disabled while the table is loading so wait for it await this.waitForListLoading(); await testSubjects.setValue('savedObjectFinderSearchInput', name); await this.waitForListLoading(); } - async panelReplaceLinkExists(name) { + async panelReplaceLinkExists(name: string) { log.debug(`DashboardReplacePanel.panelReplaceLinkExists(${name})`); await this.ensureReplacePanelIsShowing(); await this.filterEmbeddableNames(`"${name}"`); diff --git a/test/functional/services/dashboard/visualizations.js b/test/functional/services/dashboard/visualizations.ts similarity index 88% rename from test/functional/services/dashboard/visualizations.js rename to test/functional/services/dashboard/visualizations.ts index 676e4c384fe36..10747658d8c9b 100644 --- a/test/functional/services/dashboard/visualizations.js +++ b/test/functional/services/dashboard/visualizations.ts @@ -17,7 +17,9 @@ * under the License. */ -export function DashboardVisualizationProvider({ getService, getPageObjects }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function DashboardVisualizationProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const find = getService('find'); const retry = getService('retry'); @@ -34,7 +36,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { ]); return new (class DashboardVisualizations { - async createAndAddTSVBVisualization(name) { + async createAndAddTSVBVisualization(name: string) { log.debug(`createAndAddTSVBVisualization(${name})`); const inViewMode = await PageObjects.dashboard.getIsInViewMode(); if (inViewMode) { @@ -46,7 +48,15 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccess(name); } - async createSavedSearch({ name, query, fields }) { + async createSavedSearch({ + name, + query, + fields, + }: { + name: string; + query?: string; + fields?: string[]; + }) { log.debug(`createSavedSearch(${name})`); await PageObjects.header.clickDiscover(); @@ -68,7 +78,15 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { await testSubjects.exists('saveSearchSuccess'); } - async createAndAddSavedSearch({ name, query, fields }) { + async createAndAddSavedSearch({ + name, + query, + fields, + }: { + name: string; + query?: string; + fields?: string[]; + }) { log.debug(`createAndAddSavedSearch(${name})`); await this.createSavedSearch({ name, query, fields }); @@ -106,7 +124,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { } } - async createAndAddMarkdown({ name, markdown }) { + async createAndAddMarkdown({ name, markdown }: { name: string; markdown: string }) { log.debug(`createAndAddMarkdown(${markdown})`); const inViewMode = await PageObjects.dashboard.getIsInViewMode(); if (inViewMode) { diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index cbb0c6790dbe9..7891a6b00f729 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -35,10 +35,8 @@ import { DashboardExpectProvider, DashboardPanelActionsProvider, DashboardVisualizationProvider, - // @ts-ignore not TS yet } from './dashboard'; import { DocTableProvider } from './doc_table'; -import { ElasticChartProvider } from './elastic_chart'; import { EmbeddingProvider } from './embedding'; import { FilterBarProvider } from './filter_bar'; import { FlyoutProvider } from './flyout'; @@ -49,8 +47,7 @@ import { RemoteProvider } from './remote'; import { RenderableProvider } from './renderable'; import { TableProvider } from './table'; import { ToastsProvider } from './toasts'; -// @ts-ignore not TS yet -import { PieChartProvider } from './visualizations'; +import { PieChartProvider, ElasticChartProvider } from './visualizations'; import { ListingTableProvider } from './listing_table'; import { SavedQueryManagementComponentProvider } from './saved_query_management_component'; import { KibanaSupertestProvider } from './supertest'; diff --git a/test/functional/services/elastic_chart.ts b/test/functional/services/visualizations/elastic_chart.ts similarity index 97% rename from test/functional/services/elastic_chart.ts rename to test/functional/services/visualizations/elastic_chart.ts index 1c3071ac01587..3c454f0a88e24 100644 --- a/test/functional/services/elastic_chart.ts +++ b/test/functional/services/visualizations/elastic_chart.ts @@ -18,7 +18,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrProviderContext } from '../../ftr_provider_context'; export function ElasticChartProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); diff --git a/test/functional/services/visualizations/index.js b/test/functional/services/visualizations/index.ts similarity index 93% rename from test/functional/services/visualizations/index.js rename to test/functional/services/visualizations/index.ts index 1cc9d40abeab6..1da1691b07c2a 100644 --- a/test/functional/services/visualizations/index.js +++ b/test/functional/services/visualizations/index.ts @@ -18,3 +18,4 @@ */ export { PieChartProvider } from './pie_chart'; +export { ElasticChartProvider } from './elastic_chart'; diff --git a/test/functional/services/visualizations/pie_chart.js b/test/functional/services/visualizations/pie_chart.ts similarity index 78% rename from test/functional/services/visualizations/pie_chart.js rename to test/functional/services/visualizations/pie_chart.ts index edabc7ce989c0..66f32d246b31f 100644 --- a/test/functional/services/visualizations/pie_chart.js +++ b/test/functional/services/visualizations/pie_chart.ts @@ -18,8 +18,9 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export function PieChartProvider({ getService }) { +export function PieChartProvider({ getService }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); const config = getService('config'); @@ -29,7 +30,7 @@ export function PieChartProvider({ getService }) { const defaultFindTimeout = config.get('timeouts.find'); return new (class PieChart { - async filterOnPieSlice(name) { + async filterOnPieSlice(name?: string) { log.debug(`PieChart.filterOnPieSlice(${name})`); if (name) { await testSubjects.click(`pieSlice-${name.split(' ').join('-')}`); @@ -43,27 +44,27 @@ export function PieChartProvider({ getService }) { } } - async filterByLegendItem(label) { + async filterByLegendItem(label: string) { log.debug(`PieChart.filterByLegendItem(${label})`); await testSubjects.click(`legend-${label}`); await testSubjects.click(`legend-${label}-filterIn`); } - async getPieSlice(name) { + async getPieSlice(name: string) { return await testSubjects.find(`pieSlice-${name.split(' ').join('-')}`); } - async getAllPieSlices(name) { + async getAllPieSlices(name: string) { return await testSubjects.findAll(`pieSlice-${name.split(' ').join('-')}`); } - async getPieSliceStyle(name) { + async getPieSliceStyle(name: string) { log.debug(`VisualizePage.getPieSliceStyle(${name})`); const pieSlice = await this.getPieSlice(name); return await pieSlice.getAttribute('style'); } - async getAllPieSliceStyles(name) { + async getAllPieSliceStyles(name: string) { log.debug(`VisualizePage.getAllPieSliceStyles(${name})`); const pieSlices = await this.getAllPieSlices(name); return await Promise.all( @@ -73,12 +74,10 @@ export function PieChartProvider({ getService }) { async getPieChartData() { const chartTypes = await find.allByCssSelector('path.slice', defaultFindTimeout * 2); - - const getChartTypesPromises = chartTypes.map(async (chart) => await chart.getAttribute('d')); - return await Promise.all(getChartTypesPromises); + return await Promise.all(chartTypes.map(async (chart) => await chart.getAttribute('d'))); } - async expectPieChartTableData(expectedTableData) { + async expectPieChartTableData(expectedTableData: Array<[]>) { await inspector.open(); await inspector.setTablePageSize(50); await inspector.expectTableData(expectedTableData); @@ -86,22 +85,18 @@ export function PieChartProvider({ getService }) { async getPieChartLabels() { const chartTypes = await find.allByCssSelector('path.slice', defaultFindTimeout * 2); - - const getChartTypesPromises = chartTypes.map( - async (chart) => await chart.getAttribute('data-label') + return await Promise.all( + chartTypes.map(async (chart) => await chart.getAttribute('data-label')) ); - return await Promise.all(getChartTypesPromises); } async getPieSliceCount() { log.debug('PieChart.getPieSliceCount'); - return await retry.try(async () => { - const slices = await find.allByCssSelector('svg > g > g.arcs > path.slice', 2500); - return slices.length; - }); + const slices = await find.allByCssSelector('svg > g > g.arcs > path.slice'); + return slices.length; } - async expectPieSliceCount(expectedCount) { + async expectPieSliceCount(expectedCount: number) { log.debug(`PieChart.expectPieSliceCount(${expectedCount})`); await retry.try(async () => { const slicesCount = await this.getPieSliceCount(); @@ -109,7 +104,7 @@ export function PieChartProvider({ getService }) { }); } - async expectPieChartLabels(expectedLabels) { + async expectPieChartLabels(expectedLabels: string[]) { log.debug(`PieChart.expectPieChartLabels(${expectedLabels.join(',')})`); await retry.try(async () => { const pieData = await this.getPieChartLabels(); diff --git a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx index 96297f6d51566..494570b26f561 100644 --- a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx +++ b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx @@ -19,7 +19,7 @@ import * as React from 'react'; import ReactDOM from 'react-dom'; -import { HashRouter as Router, Switch, Route, Link } from 'react-router-dom'; +import { Router, Switch, Route, Link } from 'react-router-dom'; import { CoreSetup, Plugin } from 'kibana/public'; import { ManagementSetup, ManagementSectionId } from '../../../../../src/plugins/management/public'; @@ -34,19 +34,19 @@ export class ManagementTestPlugin mount(params: any) { params.setBreadcrumbs([{ text: 'Management Test' }]); ReactDOM.render( - +

Hello from management test plugin

- - - Link to /one - - - + Link to basePath + + + Link to /one + +
, params.element diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index 64d27103e59e2..6d31889a9cbe4 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -135,7 +135,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide expect(wrapperHeight).to.be.below(windowHeight); }); - it('can navigate from NP apps to legacy apps', async () => { + // Not sure if we need this test or not. If yes, we need to find another legacy app + it.skip('can navigate from NP apps to legacy apps', async () => { await appsMenu.clickLink('Stack Management'); await testSubjects.existOrFail('managementNav'); }); diff --git a/test/plugin_functional/test_suites/management/management_plugin.js b/test/plugin_functional/test_suites/management/management_plugin.js index 87542c97a3f5d..8d879cfa67d7a 100644 --- a/test/plugin_functional/test_suites/management/management_plugin.js +++ b/test/plugin_functional/test_suites/management/management_plugin.js @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }) { describe('management plugin', function describeIndexTests() { before(async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management'); + await PageObjects.common.navigateToActualUrl('management'); }); it('should be able to navigate to management test app', async () => { @@ -38,11 +38,12 @@ export default function ({ getService, getPageObjects }) { }); it('should redirect when app is disabled', async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - 'management/data/test-management-disabled' - ); - await testSubjects.existOrFail('management-landing'); + await PageObjects.common.navigateToUrl('management', 'data/test-management-disabled', { + useActualUrl: true, + shouldUseHashForSubUrl: false, + }); + + await testSubjects.existOrFail('managementHome'); }); }); } diff --git a/vars/kibanaCoverage.groovy b/vars/kibanaCoverage.groovy index 6ef8f6fc64cd5..0305f86475a9a 100644 --- a/vars/kibanaCoverage.groovy +++ b/vars/kibanaCoverage.groovy @@ -96,7 +96,7 @@ def collectVcsInfo(title) { ) } -def bootMergeAndIngest(buildNum, buildUrl, title) { +def generateReports(title) { kibanaPipeline.bash(""" source src/dev/ci_setup/setup_env.sh # bootstrap from x-pack folder @@ -108,6 +108,28 @@ def bootMergeAndIngest(buildNum, buildUrl, title) { . src/dev/code_coverage/shell_scripts/fix_html_reports_parallel.sh . src/dev/code_coverage/shell_scripts/merge_jest_and_functional.sh . src/dev/code_coverage/shell_scripts/copy_mocha_reports.sh + # zip combined reports + tar -czf kibana-coverage.tar.gz target/kibana-coverage/**/* + """, title) +} + +def uploadCombinedReports() { + kibanaPipeline.bash(""" + ls -laR target/kibana-coverage/ + """, "### List Combined Reports" + ) + + kibanaPipeline.uploadGcsArtifact( + "kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/coverage/combined", + 'kibana-coverage.tar.gz' + ) +} + +def ingestData(buildNum, buildUrl, title) { + kibanaPipeline.bash(""" + source src/dev/ci_setup/setup_env.sh + yarn kbn bootstrap --prefer-offline + # Using existing target/kibana-coverage folder . src/dev/code_coverage/shell_scripts/ingest_coverage.sh ${buildNum} ${buildUrl} """, title) } @@ -117,7 +139,7 @@ def ingestWithVault(buildNum, buildUrl, title) { withVaultSecret(secret: vaultSecret, secret_field: 'host', variable_name: 'HOST_FROM_VAULT') { withVaultSecret(secret: vaultSecret, secret_field: 'username', variable_name: 'USER_FROM_VAULT') { withVaultSecret(secret: vaultSecret, secret_field: 'password', variable_name: 'PASS_FROM_VAULT') { - bootMergeAndIngest(buildNum, buildUrl, title) + ingestData(buildNum, buildUrl, title) } } } diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 7ac27dd47ad64..a479b08ef9069 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -4,7 +4,7 @@ "xpack.actions": "plugins/actions", "xpack.advancedUiActions": "plugins/advanced_ui_actions", "xpack.uiActionsEnhanced": "examples/ui_actions_enhanced_examples", - "xpack.alerting": "plugins/alerting", + "xpack.alerts": "plugins/alerts", "xpack.alertingBuiltins": "plugins/alerting_builtins", "xpack.apm": ["legacy/plugins/apm", "plugins/apm"], "xpack.beatsManagement": ["legacy/plugins/beats_management", "plugins/beats_management"], diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index 3d8b45e7d1b83..a222e11d28f4a 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { +export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirectory }) { const fileMockPath = `${kibanaDirectory}/src/dev/jest/mocks/file_mock.js`; return { - rootDir: xPackKibanaDirectory, + rootDir, roots: ['/plugins', '/legacy/plugins', '/legacy/server'], moduleFileExtensions: ['js', 'json', 'ts', 'tsx'], moduleNameMapper: { @@ -44,15 +44,15 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { '!**/plugins/apm/e2e/**', ], coveragePathIgnorePatterns: ['.*\\.d\\.ts'], - coverageDirectory: '/../target/kibana-coverage/jest', + coverageDirectory: `${kibanaDirectory}/target/kibana-coverage/jest`, coverageReporters: !!process.env.CODE_COVERAGE ? ['json'] : ['html'], setupFiles: [ `${kibanaDirectory}/src/dev/jest/setup/babel_polyfill.js`, - `/dev-tools/jest/setup/polyfills.js`, - `/dev-tools/jest/setup/enzyme.js`, + `${xPackKibanaDirectory}/dev-tools/jest/setup/polyfills.js`, + `${xPackKibanaDirectory}/dev-tools/jest/setup/enzyme.js`, ], setupFilesAfterEnv: [ - `/dev-tools/jest/setup/setup_test.js`, + `${xPackKibanaDirectory}/dev-tools/jest/setup/setup_test.js`, `${kibanaDirectory}/src/dev/jest/setup/mocks.js`, `${kibanaDirectory}/src/dev/jest/setup/react_testing_library.js`, ], diff --git a/x-pack/dev-tools/jest/index.js b/x-pack/dev-tools/jest/index.js index f61c50f989503..2f831e33cdd11 100644 --- a/x-pack/dev-tools/jest/index.js +++ b/x-pack/dev-tools/jest/index.js @@ -14,6 +14,7 @@ export function runJest() { const config = JSON.stringify( createJestConfig({ kibanaDirectory: resolve(__dirname, '../../..'), + rootDir: resolve(__dirname, '../..'), xPackKibanaDirectory: resolve(__dirname, '../..'), }) ); diff --git a/x-pack/legacy/plugins/monitoring/index.ts b/x-pack/legacy/plugins/monitoring/index.ts index 1a0fecb9ef5b5..ee31a3037a0cb 100644 --- a/x-pack/legacy/plugins/monitoring/index.ts +++ b/x-pack/legacy/plugins/monitoring/index.ts @@ -15,7 +15,7 @@ import { KIBANA_ALERTING_ENABLED } from '../../../plugins/monitoring/common/cons */ const deps = ['kibana', 'elasticsearch', 'xpack_main']; if (KIBANA_ALERTING_ENABLED) { - deps.push(...['alerting', 'actions']); + deps.push(...['alerts', 'actions']); } export const monitoring = (kibana: any) => { return new kibana.Plugin({ diff --git a/x-pack/package.json b/x-pack/package.json index dc23602bac86c..c46d364e0ac46 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -128,7 +128,7 @@ "cheerio": "0.22.0", "commander": "3.0.2", "copy-webpack-plugin": "^5.0.4", - "cypress": "^4.4.1", + "cypress": "4.5.0", "cypress-multi-reporters": "^1.2.3", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", @@ -194,7 +194,7 @@ "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.3.0", "@elastic/node-crypto": "1.1.1", - "@elastic/numeral": "2.4.0", + "@elastic/numeral": "^2.5.0", "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", "@kbn/i18n": "1.0.0", diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index 847172ae972fd..96d5f04ac088f 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -26,7 +26,7 @@ Table of Contents - [Executor](#executor) - [Example](#example) - [RESTful API](#restful-api) - - [`POST /api/action`: Create action](#post-apiaction-create-action) + - [`POST /api/actions/action`: Create action](#post-apiaction-create-action) - [`DELETE /api/actions/action/{id}`: Delete action](#delete-apiactionid-delete-action) - [`GET /api/actions`: Get all actions](#get-apiactiongetall-get-all-actions) - [`GET /api/actions/action/{id}`: Get action](#get-apiactionid-get-action) @@ -163,7 +163,7 @@ The built-in email action type provides a good example of creating an action typ Using an action type requires an action to be created that will contain and encrypt configuration for a given action type. See below for CRUD operations using the API. -### `POST /api/action`: Create action +### `POST /api/actions/action`: Create action Payload: diff --git a/x-pack/plugins/alerting_builtins/README.md b/x-pack/plugins/alerting_builtins/README.md index 233984a1ff23f..2944247e4714c 100644 --- a/x-pack/plugins/alerting_builtins/README.md +++ b/x-pack/plugins/alerting_builtins/README.md @@ -1,7 +1,7 @@ # alerting_builtins plugin This plugin provides alertTypes shipped with Kibana for use with the -[the alerting plugin](../alerting/README.md). When enabled, it will register +[the alerts plugin](../alerts/README.md). When enabled, it will register the built-in alertTypes with the alerting plugin, register associated HTTP routes, etc. diff --git a/x-pack/plugins/alerting_builtins/kibana.json b/x-pack/plugins/alerting_builtins/kibana.json index 78de9a1ae0165..cc613d5247ef4 100644 --- a/x-pack/plugins/alerting_builtins/kibana.json +++ b/x-pack/plugins/alerting_builtins/kibana.json @@ -3,7 +3,7 @@ "server": true, "version": "8.0.0", "kibanaVersion": "kibana", - "requiredPlugins": ["alerting"], + "requiredPlugins": ["alerts"], "configPath": ["xpack", "alerting_builtins"], "ui": false } diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index.ts index 475efc87b443a..d9232195b0f52 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index.ts @@ -10,7 +10,7 @@ import { register as registerIndexThreshold } from './index_threshold'; interface RegisterBuiltInAlertTypesParams { service: Service; router: IRouter; - alerting: AlertingSetup; + alerts: AlertingSetup; baseRoute: string; } diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts index 15139ae34c93d..c3a132bc609d6 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { Params } from './alert_type_params'; -import { AlertExecutorOptions } from '../../../../alerting/server'; +import { AlertExecutorOptions } from '../../../../alerts/server'; // alert type context provided to actions diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/index.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/index.ts index fbe107054ce9d..9787ece323c59 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/index.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/index.ts @@ -23,14 +23,14 @@ export function getService() { interface RegisterParams { service: Service; router: IRouter; - alerting: AlertingSetup; + alerts: AlertingSetup; baseRoute: string; } export function register(params: RegisterParams) { - const { service, router, alerting, baseRoute } = params; + const { service, router, alerts, baseRoute } = params; - alerting.registerType(getAlertType(service)); + alerts.registerType(getAlertType(service)); const baseBuiltInRoute = `${baseRoute}/index_threshold`; registerRoutes({ service, router, baseRoute: baseBuiltInRoute }); diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/date_range_info.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/date_range_info.ts index 0a4accc983d79..fa991786a60b6 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/date_range_info.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/date_range_info.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { times } from 'lodash'; -import { parseDuration } from '../../../../../alerting/server'; +import { parseDuration } from '../../../../../alerts/server'; import { MAX_INTERVALS } from '../index'; // dates as numbers are epoch millis diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts index 40e6f187ce18f..a22395cb0961b 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/lib/time_series_types.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; -import { parseDuration } from '../../../../../alerting/server'; +import { parseDuration } from '../../../../../alerts/server'; import { MAX_INTERVALS } from '../index'; import { CoreQueryParamsSchemaProperties, validateCoreQueryBody } from './core_query_types'; import { diff --git a/x-pack/plugins/alerting_builtins/server/plugin.test.ts b/x-pack/plugins/alerting_builtins/server/plugin.test.ts index f93041fa3c142..71a904dcbab3d 100644 --- a/x-pack/plugins/alerting_builtins/server/plugin.test.ts +++ b/x-pack/plugins/alerting_builtins/server/plugin.test.ts @@ -6,7 +6,7 @@ import { AlertingBuiltinsPlugin } from './plugin'; import { coreMock } from '../../../../src/core/server/mocks'; -import { alertsMock } from '../../../plugins/alerting/server/mocks'; +import { alertsMock } from '../../alerts/server/mocks'; describe('AlertingBuiltins Plugin', () => { describe('setup()', () => { @@ -22,7 +22,7 @@ describe('AlertingBuiltins Plugin', () => { it('should register built-in alert types', async () => { const alertingSetup = alertsMock.createSetup(); - await plugin.setup(coreSetup, { alerting: alertingSetup }); + await plugin.setup(coreSetup, { alerts: alertingSetup }); expect(alertingSetup.registerType).toHaveBeenCalledTimes(1); @@ -44,7 +44,7 @@ describe('AlertingBuiltins Plugin', () => { it('should return a service in the expected shape', async () => { const alertingSetup = alertsMock.createSetup(); - const service = await plugin.setup(coreSetup, { alerting: alertingSetup }); + const service = await plugin.setup(coreSetup, { alerts: alertingSetup }); expect(typeof service.indexThreshold.timeSeriesQuery).toBe('function'); }); diff --git a/x-pack/plugins/alerting_builtins/server/plugin.ts b/x-pack/plugins/alerting_builtins/server/plugin.ts index 9a9483f9c9dfa..12d1b080c7c63 100644 --- a/x-pack/plugins/alerting_builtins/server/plugin.ts +++ b/x-pack/plugins/alerting_builtins/server/plugin.ts @@ -22,11 +22,11 @@ export class AlertingBuiltinsPlugin implements Plugin { }; } - public async setup(core: CoreSetup, { alerting }: AlertingBuiltinsDeps): Promise { + public async setup(core: CoreSetup, { alerts }: AlertingBuiltinsDeps): Promise { registerBuiltInAlertTypes({ service: this.service, router: core.http.createRouter(), - alerting, + alerts, baseRoute: '/api/alerting_builtins', }); return this.service; diff --git a/x-pack/plugins/alerting_builtins/server/types.ts b/x-pack/plugins/alerting_builtins/server/types.ts index ff07b85fd3038..95d34371a6d1e 100644 --- a/x-pack/plugins/alerting_builtins/server/types.ts +++ b/x-pack/plugins/alerting_builtins/server/types.ts @@ -5,7 +5,7 @@ */ import { Logger, ScopedClusterClient } from '../../../../src/core/server'; -import { PluginSetupContract as AlertingSetup } from '../../alerting/server'; +import { PluginSetupContract as AlertingSetup } from '../../alerts/server'; import { getService as getServiceIndexThreshold } from './alert_types/index_threshold'; export { Logger, IRouter } from '../../../../src/core/server'; @@ -14,11 +14,11 @@ export { PluginSetupContract as AlertingSetup, AlertType, AlertExecutorOptions, -} from '../../alerting/server'; +} from '../../alerts/server'; // this plugin's dependendencies export interface AlertingBuiltinsDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; } // external service exposed through plugin setup/start diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerts/README.md similarity index 91% rename from x-pack/plugins/alerting/README.md rename to x-pack/plugins/alerts/README.md index dfa2991895429..811478426a8d3 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerts/README.md @@ -20,20 +20,20 @@ Table of Contents - [Example](#example) - [Alert Navigation](#alert-navigation) - [RESTful API](#restful-api) - - [`POST /api/alert`: Create alert](#post-apialert-create-alert) - - [`DELETE /api/alert/{id}`: Delete alert](#delete-apialertid-delete-alert) - - [`GET /api/alert/_find`: Find alerts](#get-apialertfind-find-alerts) - - [`GET /api/alert/{id}`: Get alert](#get-apialertid-get-alert) - - [`GET /api/alert/{id}/state`: Get alert state](#get-apialertidstate-get-alert-state) - - [`GET /api/alert/types`: List alert types](#get-apialerttypes-list-alert-types) - - [`PUT /api/alert/{id}`: Update alert](#put-apialertid-update-alert) - - [`POST /api/alert/{id}/_enable`: Enable an alert](#post-apialertidenable-enable-an-alert) - - [`POST /api/alert/{id}/_disable`: Disable an alert](#post-apialertiddisable-disable-an-alert) - - [`POST /api/alert/{id}/_mute_all`: Mute all alert instances](#post-apialertidmuteall-mute-all-alert-instances) - - [`POST /api/alert/{alertId}/alert_instance/{alertInstanceId}/_mute`: Mute alert instance](#post-apialertalertidalertinstancealertinstanceidmute-mute-alert-instance) - - [`POST /api/alert/{id}/_unmute_all`: Unmute all alert instances](#post-apialertidunmuteall-unmute-all-alert-instances) - - [`POST /api/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance](#post-apialertalertidalertinstancealertinstanceidunmute-unmute-an-alert-instance) - - [`POST /api/alert/{id}/_update_api_key`: Update alert API key](#post-apialertidupdateapikey-update-alert-api-key) + - [`POST /api/alerts/alert`: Create alert](#post-apialert-create-alert) + - [`DELETE /api/alerts/alert/{id}`: Delete alert](#delete-apialertid-delete-alert) + - [`GET /api/alerts/_find`: Find alerts](#get-apialertfind-find-alerts) + - [`GET /api/alerts/alert/{id}`: Get alert](#get-apialertid-get-alert) + - [`GET /api/alerts/alert/{id}/state`: Get alert state](#get-apialertidstate-get-alert-state) + - [`GET /api/alerts/list_alert_types`: List alert types](#get-apialerttypes-list-alert-types) + - [`PUT /api/alerts/alert/{id}`: Update alert](#put-apialertid-update-alert) + - [`POST /api/alerts/alert/{id}/_enable`: Enable an alert](#post-apialertidenable-enable-an-alert) + - [`POST /api/alerts/alert/{id}/_disable`: Disable an alert](#post-apialertiddisable-disable-an-alert) + - [`POST /api/alerts/alert/{id}/_mute_all`: Mute all alert instances](#post-apialertidmuteall-mute-all-alert-instances) + - [`POST /api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`: Mute alert instance](#post-apialertalertidalertinstancealertinstanceidmute-mute-alert-instance) + - [`POST /api/alerts/alert/{id}/_unmute_all`: Unmute all alert instances](#post-apialertidunmuteall-unmute-all-alert-instances) + - [`POST /api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance](#post-apialertalertidalertinstancealertinstanceidunmute-unmute-an-alert-instance) + - [`POST /api/alerts/alert/{id}/_update_api_key`: Update alert API key](#post-apialertidupdateapikey-update-alert-api-key) - [Schedule Formats](#schedule-formats) - [Alert instance factory](#alert-instance-factory) - [Templating actions](#templating-actions) @@ -78,7 +78,7 @@ Note that the `manage_own_api_key` cluster privilege is not enough - it can be u ### Methods -**server.newPlatform.setup.plugins.alerting.registerType(options)** +**server.newPlatform.setup.plugins.alerts.registerType(options)** The following table describes the properties of the `options` object. @@ -139,7 +139,7 @@ This example receives server and threshold as parameters. It will read the CPU u ```typescript import { schema } from '@kbn/config-schema'; ... -server.newPlatform.setup.plugins.alerting.registerType({ +server.newPlatform.setup.plugins.alerts.registerType({ id: 'my-alert-type', name: 'My alert type', validate: { @@ -220,7 +220,7 @@ server.newPlatform.setup.plugins.alerting.registerType({ This example only receives threshold as a parameter. It will read the CPU usage of all the servers and schedule individual actions if the reading for a server is greater than the threshold. This is a better implementation than above as only one query is performed for all the servers instead of one query per server. ```typescript -server.newPlatform.setup.plugins.alerting.registerType({ +server.newPlatform.setup.plugins.alerts.registerType({ id: 'my-alert-type', name: 'My alert type', validate: { @@ -352,7 +352,7 @@ You can use the `registerNavigation` api to specify as many AlertType specific h Using an alert type requires you to create an alert that will contain parameters and actions for a given alert type. See below for CRUD operations using the API. -### `POST /api/alert`: Create alert +### `POST /api/alerts/alert`: Create alert Payload: @@ -367,7 +367,7 @@ Payload: |params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| |actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): The map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| -### `DELETE /api/alert/{id}`: Delete alert +### `DELETE /api/alerts/alert/{id}`: Delete alert Params: @@ -375,13 +375,13 @@ Params: |---|---|---| |id|The id of the alert you're trying to delete.|string| -### `GET /api/alert/_find`: Find alerts +### `GET /api/alerts/_find`: Find alerts Params: See the saved objects API documentation for find. All the properties are the same except you cannot pass in `type`. -### `GET /api/alert/{id}`: Get alert +### `GET /api/alerts/alert/{id}`: Get alert Params: @@ -389,7 +389,7 @@ Params: |---|---|---| |id|The id of the alert you're trying to get.|string| -### `GET /api/alert/{id}/state`: Get alert state +### `GET /api/alerts/alert/{id}/state`: Get alert state Params: @@ -397,11 +397,11 @@ Params: |---|---|---| |id|The id of the alert whose state you're trying to get.|string| -### `GET /api/alert/types`: List alert types +### `GET /api/alerts/list_alert_types`: List alert types No parameters. -### `PUT /api/alert/{id}`: Update alert +### `PUT /api/alerts/alert/{id}`: Update alert Params: @@ -420,7 +420,7 @@ Payload: |params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| |actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): There map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| -### `POST /api/alert/{id}/_enable`: Enable an alert +### `POST /api/alerts/alert/{id}/_enable`: Enable an alert Params: @@ -428,7 +428,7 @@ Params: |---|---|---| |id|The id of the alert you're trying to enable.|string| -### `POST /api/alert/{id}/_disable`: Disable an alert +### `POST /api/alerts/alert/{id}/_disable`: Disable an alert Params: @@ -436,7 +436,7 @@ Params: |---|---|---| |id|The id of the alert you're trying to disable.|string| -### `POST /api/alert/{id}/_mute_all`: Mute all alert instances +### `POST /api/alerts/alert/{id}/_mute_all`: Mute all alert instances Params: @@ -444,7 +444,7 @@ Params: |---|---|---| |id|The id of the alert you're trying to mute all alert instances for.|string| -### `POST /api/alert/{alertId}/alert_instance/{alertInstanceId}/_mute`: Mute alert instance +### `POST /api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`: Mute alert instance Params: @@ -453,7 +453,7 @@ Params: |alertId|The id of the alert you're trying to mute an instance for.|string| |alertInstanceId|The instance id of the alert instance you're trying to mute.|string| -### `POST /api/alert/{id}/_unmute_all`: Unmute all alert instances +### `POST /api/alerts/alert/{id}/_unmute_all`: Unmute all alert instances Params: @@ -461,7 +461,7 @@ Params: |---|---|---| |id|The id of the alert you're trying to unmute all alert instances for.|string| -### `POST /api/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance +### `POST /api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance Params: @@ -470,7 +470,7 @@ Params: |alertId|The id of the alert you're trying to unmute an instance for.|string| |alertInstanceId|The instance id of the alert instance you're trying to unmute.|string| -### `POST /api/alert/{id}/_update_api_key`: Update alert API key +### `POST /api/alerts/alert/{id}/_update_api_key`: Update alert API key |Property|Description|Type| |---|---|---| diff --git a/x-pack/plugins/alerting/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts similarity index 100% rename from x-pack/plugins/alerting/common/alert.ts rename to x-pack/plugins/alerts/common/alert.ts diff --git a/x-pack/plugins/alerting/common/alert_instance.ts b/x-pack/plugins/alerts/common/alert_instance.ts similarity index 100% rename from x-pack/plugins/alerting/common/alert_instance.ts rename to x-pack/plugins/alerts/common/alert_instance.ts diff --git a/x-pack/plugins/alerting/common/alert_navigation.ts b/x-pack/plugins/alerts/common/alert_navigation.ts similarity index 100% rename from x-pack/plugins/alerting/common/alert_navigation.ts rename to x-pack/plugins/alerts/common/alert_navigation.ts diff --git a/x-pack/plugins/alerting/common/alert_task_instance.ts b/x-pack/plugins/alerts/common/alert_task_instance.ts similarity index 100% rename from x-pack/plugins/alerting/common/alert_task_instance.ts rename to x-pack/plugins/alerts/common/alert_task_instance.ts diff --git a/x-pack/plugins/alerting/common/alert_type.ts b/x-pack/plugins/alerts/common/alert_type.ts similarity index 100% rename from x-pack/plugins/alerting/common/alert_type.ts rename to x-pack/plugins/alerts/common/alert_type.ts diff --git a/x-pack/plugins/alerting/common/date_from_string.test.ts b/x-pack/plugins/alerts/common/date_from_string.test.ts similarity index 100% rename from x-pack/plugins/alerting/common/date_from_string.test.ts rename to x-pack/plugins/alerts/common/date_from_string.test.ts diff --git a/x-pack/plugins/alerting/common/date_from_string.ts b/x-pack/plugins/alerts/common/date_from_string.ts similarity index 100% rename from x-pack/plugins/alerting/common/date_from_string.ts rename to x-pack/plugins/alerts/common/date_from_string.ts diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerts/common/index.ts similarity index 92% rename from x-pack/plugins/alerting/common/index.ts rename to x-pack/plugins/alerts/common/index.ts index 2574e73dd4f9a..88a8da5a3e575 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerts/common/index.ts @@ -20,4 +20,4 @@ export interface AlertingFrameworkHealth { hasPermanentEncryptionKey: boolean; } -export const BASE_ALERT_API_PATH = '/api/alert'; +export const BASE_ALERT_API_PATH = '/api/alerts'; diff --git a/x-pack/plugins/alerting/common/parse_duration.test.ts b/x-pack/plugins/alerts/common/parse_duration.test.ts similarity index 100% rename from x-pack/plugins/alerting/common/parse_duration.test.ts rename to x-pack/plugins/alerts/common/parse_duration.test.ts diff --git a/x-pack/plugins/alerting/common/parse_duration.ts b/x-pack/plugins/alerts/common/parse_duration.ts similarity index 100% rename from x-pack/plugins/alerting/common/parse_duration.ts rename to x-pack/plugins/alerts/common/parse_duration.ts diff --git a/x-pack/plugins/alerting/kibana.json b/x-pack/plugins/alerts/kibana.json similarity index 80% rename from x-pack/plugins/alerting/kibana.json rename to x-pack/plugins/alerts/kibana.json index 59c4bb2221b0a..3509f79dbbe4d 100644 --- a/x-pack/plugins/alerting/kibana.json +++ b/x-pack/plugins/alerts/kibana.json @@ -1,10 +1,10 @@ { - "id": "alerting", + "id": "alerts", "server": true, "ui": true, "version": "8.0.0", "kibanaVersion": "kibana", - "configPath": ["xpack", "alerting"], + "configPath": ["xpack", "alerts"], "requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "actions", "eventLog"], "optionalPlugins": ["usageCollection", "spaces", "security"] } diff --git a/x-pack/plugins/alerting/public/alert_api.test.ts b/x-pack/plugins/alerts/public/alert_api.test.ts similarity index 92% rename from x-pack/plugins/alerting/public/alert_api.test.ts rename to x-pack/plugins/alerts/public/alert_api.test.ts index 1149e6fc249a9..45b9f5ba8fe2e 100644 --- a/x-pack/plugins/alerting/public/alert_api.test.ts +++ b/x-pack/plugins/alerts/public/alert_api.test.ts @@ -31,7 +31,7 @@ describe('loadAlertTypes', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/types", + "/api/alerts/list_alert_types", ] `); }); @@ -53,7 +53,7 @@ describe('loadAlertType', () => { expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/types", + "/api/alerts/list_alert_types", ] `); }); @@ -111,7 +111,7 @@ describe('loadAlert', () => { http.get.mockResolvedValueOnce(resolvedValue); expect(await loadAlert({ http, alertId })).toEqual(resolvedValue); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}`); }); }); @@ -130,7 +130,7 @@ describe('loadAlertState', () => { http.get.mockResolvedValueOnce(resolvedValue); expect(await loadAlertState({ http, alertId })).toEqual(resolvedValue); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); test('should parse AlertInstances', async () => { @@ -167,7 +167,7 @@ describe('loadAlertState', () => { }, }, }); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); test('should handle empty response from api', async () => { @@ -175,6 +175,6 @@ describe('loadAlertState', () => { http.get.mockResolvedValueOnce(''); expect(await loadAlertState({ http, alertId })).toEqual({}); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); }); diff --git a/x-pack/plugins/alerting/public/alert_api.ts b/x-pack/plugins/alerts/public/alert_api.ts similarity index 84% rename from x-pack/plugins/alerting/public/alert_api.ts rename to x-pack/plugins/alerts/public/alert_api.ts index ee9432885d671..5b7cd2128f386 100644 --- a/x-pack/plugins/alerting/public/alert_api.ts +++ b/x-pack/plugins/alerts/public/alert_api.ts @@ -16,7 +16,7 @@ import { BASE_ALERT_API_PATH, alertStateSchema } from '../common'; import { Alert, AlertType, AlertTaskState } from '../common'; export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/types`); + return await http.get(`${BASE_ALERT_API_PATH}/list_alert_types`); } export async function loadAlertType({ @@ -27,11 +27,11 @@ export async function loadAlertType({ id: AlertType['id']; }): Promise { const maybeAlertType = findFirst((type) => type.id === id)( - await http.get(`${BASE_ALERT_API_PATH}/types`) + await http.get(`${BASE_ALERT_API_PATH}/list_alert_types`) ); if (isNone(maybeAlertType)) { throw new Error( - i18n.translate('xpack.alerting.loadAlertType.missingAlertTypeError', { + i18n.translate('xpack.alerts.loadAlertType.missingAlertTypeError', { defaultMessage: 'Alert type "{id}" is not registered.', values: { id, @@ -49,7 +49,7 @@ export async function loadAlert({ http: HttpSetup; alertId: string; }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/${alertId}`); + return await http.get(`${BASE_ALERT_API_PATH}/alert/${alertId}`); } type EmptyHttpResponse = ''; @@ -61,7 +61,7 @@ export async function loadAlertState({ alertId: string; }): Promise { return await http - .get(`${BASE_ALERT_API_PATH}/${alertId}/state`) + .get(`${BASE_ALERT_API_PATH}/alert/${alertId}/state`) .then((state: AlertTaskState | EmptyHttpResponse) => (state ? state : {})) .then((state: AlertTaskState) => { return pipe( diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.mock.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.mock.ts similarity index 100% rename from x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.mock.ts rename to x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.mock.ts diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.test.ts similarity index 100% rename from x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts rename to x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.test.ts diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.ts similarity index 90% rename from x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts rename to x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.ts index f30629789b4ed..933ed442523c8 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts +++ b/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.ts @@ -36,7 +36,7 @@ export class AlertNavigationRegistry { public registerDefault(consumer: string, handler: AlertNavigationHandler) { if (this.hasDefaultHandler(consumer)) { throw new Error( - i18n.translate('xpack.alerting.alertNavigationRegistry.register.duplicateDefaultError', { + i18n.translate('xpack.alerts.alertNavigationRegistry.register.duplicateDefaultError', { defaultMessage: 'Default Navigation within "{consumer}" is already registered.', values: { consumer, @@ -54,7 +54,7 @@ export class AlertNavigationRegistry { public register(consumer: string, alertType: AlertType, handler: AlertNavigationHandler) { if (this.hasTypedHandler(consumer, alertType)) { throw new Error( - i18n.translate('xpack.alerting.alertNavigationRegistry.register.duplicateNavigationError', { + i18n.translate('xpack.alerts.alertNavigationRegistry.register.duplicateNavigationError', { defaultMessage: 'Navigation for Alert type "{alertType}" within "{consumer}" is already registered.', values: { @@ -78,7 +78,7 @@ export class AlertNavigationRegistry { } throw new Error( - i18n.translate('xpack.alerting.alertNavigationRegistry.get.missingNavigationError', { + i18n.translate('xpack.alerts.alertNavigationRegistry.get.missingNavigationError', { defaultMessage: 'Navigation for Alert type "{alertType}" within "{consumer}" is not registered.', values: { diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/index.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/index.ts similarity index 100% rename from x-pack/plugins/alerting/public/alert_navigation_registry/index.ts rename to x-pack/plugins/alerts/public/alert_navigation_registry/index.ts diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/types.ts similarity index 100% rename from x-pack/plugins/alerting/public/alert_navigation_registry/types.ts rename to x-pack/plugins/alerts/public/alert_navigation_registry/types.ts diff --git a/x-pack/plugins/alerting/public/index.ts b/x-pack/plugins/alerts/public/index.ts similarity index 100% rename from x-pack/plugins/alerting/public/index.ts rename to x-pack/plugins/alerts/public/index.ts diff --git a/x-pack/plugins/alerting/public/mocks.ts b/x-pack/plugins/alerts/public/mocks.ts similarity index 100% rename from x-pack/plugins/alerting/public/mocks.ts rename to x-pack/plugins/alerts/public/mocks.ts diff --git a/x-pack/plugins/alerting/public/plugin.ts b/x-pack/plugins/alerts/public/plugin.ts similarity index 100% rename from x-pack/plugins/alerting/public/plugin.ts rename to x-pack/plugins/alerts/public/plugin.ts diff --git a/x-pack/plugins/alerting/server/alert_instance/alert_instance.test.ts b/x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_instance/alert_instance.test.ts rename to x-pack/plugins/alerts/server/alert_instance/alert_instance.test.ts diff --git a/x-pack/plugins/alerting/server/alert_instance/alert_instance.ts b/x-pack/plugins/alerts/server/alert_instance/alert_instance.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_instance/alert_instance.ts rename to x-pack/plugins/alerts/server/alert_instance/alert_instance.ts diff --git a/x-pack/plugins/alerting/server/alert_instance/create_alert_instance_factory.test.ts b/x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_instance/create_alert_instance_factory.test.ts rename to x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.test.ts diff --git a/x-pack/plugins/alerting/server/alert_instance/create_alert_instance_factory.ts b/x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_instance/create_alert_instance_factory.ts rename to x-pack/plugins/alerts/server/alert_instance/create_alert_instance_factory.ts diff --git a/x-pack/plugins/alerting/server/alert_instance/index.ts b/x-pack/plugins/alerts/server/alert_instance/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_instance/index.ts rename to x-pack/plugins/alerts/server/alert_instance/index.ts diff --git a/x-pack/plugins/alerting/server/alert_type_registry.mock.ts b/x-pack/plugins/alerts/server/alert_type_registry.mock.ts similarity index 100% rename from x-pack/plugins/alerting/server/alert_type_registry.mock.ts rename to x-pack/plugins/alerts/server/alert_type_registry.mock.ts diff --git a/x-pack/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/plugins/alerts/server/alert_type_registry.test.ts similarity index 98% rename from x-pack/plugins/alerting/server/alert_type_registry.test.ts rename to x-pack/plugins/alerts/server/alert_type_registry.test.ts index e78e5ab7932c2..6d7cf621ab0ca 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.test.ts @@ -7,7 +7,7 @@ import { TaskRunnerFactory } from './task_runner'; import { AlertTypeRegistry } from './alert_type_registry'; import { AlertType } from './types'; -import { taskManagerMock } from '../../../plugins/task_manager/server/task_manager.mock'; +import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; const taskManager = taskManagerMock.setup(); const alertTypeRegistryParams = { diff --git a/x-pack/plugins/alerting/server/alert_type_registry.ts b/x-pack/plugins/alerts/server/alert_type_registry.ts similarity index 89% rename from x-pack/plugins/alerting/server/alert_type_registry.ts rename to x-pack/plugins/alerts/server/alert_type_registry.ts index 0163cb71166e8..8f36afe062aa5 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; -import { RunContext, TaskManagerSetupContract } from '../../../plugins/task_manager/server'; +import { RunContext, TaskManagerSetupContract } from '../../task_manager/server'; import { TaskRunnerFactory } from './task_runner'; import { AlertType } from './types'; @@ -32,7 +32,7 @@ export class AlertTypeRegistry { public register(alertType: AlertType) { if (this.has(alertType.id)) { throw new Error( - i18n.translate('xpack.alerting.alertTypeRegistry.register.duplicateAlertTypeError', { + i18n.translate('xpack.alerts.alertTypeRegistry.register.duplicateAlertTypeError', { defaultMessage: 'Alert type "{id}" is already registered.', values: { id: alertType.id, @@ -55,7 +55,7 @@ export class AlertTypeRegistry { public get(id: string): AlertType { if (!this.has(id)) { throw Boom.badRequest( - i18n.translate('xpack.alerting.alertTypeRegistry.get.missingAlertTypeError', { + i18n.translate('xpack.alerts.alertTypeRegistry.get.missingAlertTypeError', { defaultMessage: 'Alert type "{id}" is not registered.', values: { id, diff --git a/x-pack/plugins/alerting/server/alerts_client.mock.ts b/x-pack/plugins/alerts/server/alerts_client.mock.ts similarity index 100% rename from x-pack/plugins/alerting/server/alerts_client.mock.ts rename to x-pack/plugins/alerts/server/alerts_client.mock.ts diff --git a/x-pack/plugins/alerting/server/alerts_client.test.ts b/x-pack/plugins/alerts/server/alerts_client.test.ts similarity index 99% rename from x-pack/plugins/alerting/server/alerts_client.test.ts rename to x-pack/plugins/alerts/server/alerts_client.test.ts index 12106100602e7..9685f58b8fb31 100644 --- a/x-pack/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client.test.ts @@ -7,12 +7,12 @@ import uuid from 'uuid'; import { schema } from '@kbn/config-schema'; import { AlertsClient, CreateOptions } from './alerts_client'; import { savedObjectsClientMock, loggingServiceMock } from '../../../../src/core/server/mocks'; -import { taskManagerMock } from '../../../plugins/task_manager/server/task_manager.mock'; +import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; -import { TaskStatus } from '../../../plugins/task_manager/server'; +import { TaskStatus } from '../../task_manager/server'; import { IntervalSchedule } from './types'; import { resolvable } from './test_utils'; -import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; +import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { actionsClientMock } from '../../actions/server/mocks'; const taskManager = taskManagerMock.start(); @@ -1677,7 +1677,7 @@ describe('find()', () => { }, ], }); - const result = await alertsClient.find(); + const result = await alertsClient.find({ options: {} }); expect(result).toMatchInlineSnapshot(` Object { "data": Array [ diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client.ts similarity index 96% rename from x-pack/plugins/alerting/server/alerts_client.ts rename to x-pack/plugins/alerts/server/alerts_client.ts index 382e9d1a616ad..6b091a5a4503b 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client.ts @@ -30,9 +30,9 @@ import { InvalidateAPIKeyParams, GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult, InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, -} from '../../../plugins/security/server'; -import { EncryptedSavedObjectsClient } from '../../../plugins/encrypted_saved_objects/server'; -import { TaskManagerStartContract } from '../../../plugins/task_manager/server'; +} from '../../security/server'; +import { EncryptedSavedObjectsClient } from '../../encrypted_saved_objects/server'; +import { TaskManagerStartContract } from '../../task_manager/server'; import { taskInstanceToAlertTaskInstance } from './task_runner/alert_task_instance'; import { deleteTaskIfItExists } from './lib/delete_task_if_it_exists'; @@ -58,22 +58,29 @@ interface ConstructorOptions { getActionsClient: () => Promise; } -export interface FindOptions { - options?: { - perPage?: number; - page?: number; - search?: string; - defaultSearchOperator?: 'AND' | 'OR'; - searchFields?: string[]; - sortField?: string; - sortOrder?: string; - hasReference?: { - type: string; - id: string; - }; - fields?: string[]; - filter?: string; +export interface MuteOptions extends IndexType { + alertId: string; + alertInstanceId: string; +} + +export interface FindOptions extends IndexType { + perPage?: number; + page?: number; + search?: string; + defaultSearchOperator?: 'AND' | 'OR'; + searchFields?: string[]; + sortField?: string; + sortOrder?: string; + hasReference?: { + type: string; + id: string; }; + fields?: string[]; + filter?: string; +} + +interface IndexType { + [key: string]: unknown; } export interface FindResult { @@ -225,7 +232,7 @@ export class AlertsClient { } } - public async find({ options = {} }: FindOptions = {}): Promise { + public async find({ options = {} }: { options: FindOptions }): Promise { const { page, per_page: perPage, @@ -533,13 +540,7 @@ export class AlertsClient { }); } - public async muteInstance({ - alertId, - alertInstanceId, - }: { - alertId: string; - alertInstanceId: string; - }) { + public async muteInstance({ alertId, alertInstanceId }: MuteOptions) { const { attributes, version } = await this.savedObjectsClient.get('alert', alertId); const mutedInstanceIds = attributes.mutedInstanceIds || []; if (!attributes.muteAll && !mutedInstanceIds.includes(alertInstanceId)) { @@ -652,7 +653,7 @@ export class AlertsClient { ); if (invalidActionGroups.length) { throw Boom.badRequest( - i18n.translate('xpack.alerting.alertsClient.validateActions.invalidGroups', { + i18n.translate('xpack.alerts.alertsClient.validateActions.invalidGroups', { defaultMessage: 'Invalid action groups: {groups}', values: { groups: invalidActionGroups.join(', '), diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/alerts_client_factory.test.ts rename to x-pack/plugins/alerts/server/alerts_client_factory.test.ts index d1a7c60bb9a68..50dafba00a7e4 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts @@ -7,12 +7,12 @@ import { Request } from 'hapi'; import { AlertsClientFactory, AlertsClientFactoryOpts } from './alerts_client_factory'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; -import { taskManagerMock } from '../../../plugins/task_manager/server/task_manager.mock'; +import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; import { KibanaRequest } from '../../../../src/core/server'; import { loggingServiceMock, savedObjectsClientMock } from '../../../../src/core/server/mocks'; -import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; -import { AuthenticatedUser } from '../../../plugins/security/public'; -import { securityMock } from '../../../plugins/security/server/mocks'; +import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; +import { AuthenticatedUser } from '../../security/public'; +import { securityMock } from '../../security/server/mocks'; import { actionsMock } from '../../actions/server/mocks'; jest.mock('./alerts_client'); diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.ts b/x-pack/plugins/alerts/server/alerts_client_factory.ts similarity index 95% rename from x-pack/plugins/alerting/server/alerts_client_factory.ts rename to x-pack/plugins/alerts/server/alerts_client_factory.ts index 2924736330abd..af546f965d7df 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.ts +++ b/x-pack/plugins/alerts/server/alerts_client_factory.ts @@ -8,9 +8,9 @@ import { PluginStartContract as ActionsPluginStartContract } from '../../actions import { AlertsClient } from './alerts_client'; import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { KibanaRequest, Logger, SavedObjectsClientContract } from '../../../../src/core/server'; -import { InvalidateAPIKeyParams, SecurityPluginSetup } from '../../../plugins/security/server'; -import { EncryptedSavedObjectsClient } from '../../../plugins/encrypted_saved_objects/server'; -import { TaskManagerStartContract } from '../../../plugins/task_manager/server'; +import { InvalidateAPIKeyParams, SecurityPluginSetup } from '../../security/server'; +import { EncryptedSavedObjectsClient } from '../../encrypted_saved_objects/server'; +import { TaskManagerStartContract } from '../../task_manager/server'; export interface AlertsClientFactoryOpts { logger: Logger; diff --git a/x-pack/plugins/alerting/server/constants/plugin.ts b/x-pack/plugins/alerts/server/constants/plugin.ts similarity index 86% rename from x-pack/plugins/alerting/server/constants/plugin.ts rename to x-pack/plugins/alerts/server/constants/plugin.ts index 9c276ed1d75de..c180b68680841 100644 --- a/x-pack/plugins/alerting/server/constants/plugin.ts +++ b/x-pack/plugins/alerts/server/constants/plugin.ts @@ -7,12 +7,12 @@ import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/constants'; export const PLUGIN = { - ID: 'alerting', + ID: 'alerts', MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements // all plugins seem to use getI18nName with any // eslint-disable-next-line @typescript-eslint/no-explicit-any getI18nName: (i18n: any): string => - i18n.translate('xpack.alerting.appName', { - defaultMessage: 'Alerting', + i18n.translate('xpack.alerts.appName', { + defaultMessage: 'Alerts', }), }; diff --git a/x-pack/plugins/alerting/server/index.ts b/x-pack/plugins/alerts/server/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/index.ts rename to x-pack/plugins/alerts/server/index.ts diff --git a/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts b/x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.test.ts rename to x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.test.ts diff --git a/x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.ts b/x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/delete_task_if_it_exists.ts rename to x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.ts diff --git a/x-pack/plugins/alerting/server/lib/index.ts b/x-pack/plugins/alerts/server/lib/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/index.ts rename to x-pack/plugins/alerts/server/lib/index.ts diff --git a/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.test.ts b/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/is_alert_not_found_error.test.ts rename to x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts diff --git a/x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts b/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/is_alert_not_found_error.ts rename to x-pack/plugins/alerts/server/lib/is_alert_not_found_error.ts diff --git a/x-pack/plugins/alerting/server/lib/license_api_access.ts b/x-pack/plugins/alerts/server/lib/license_api_access.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/license_api_access.ts rename to x-pack/plugins/alerts/server/lib/license_api_access.ts diff --git a/x-pack/plugins/alerting/server/lib/license_state.mock.ts b/x-pack/plugins/alerts/server/lib/license_state.mock.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/license_state.mock.ts rename to x-pack/plugins/alerts/server/lib/license_state.mock.ts diff --git a/x-pack/plugins/alerting/server/lib/license_state.test.ts b/x-pack/plugins/alerts/server/lib/license_state.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/lib/license_state.test.ts rename to x-pack/plugins/alerts/server/lib/license_state.test.ts index cbab98a6311dd..50b4e6b4439f7 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.test.ts +++ b/x-pack/plugins/alerts/server/lib/license_state.test.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { LicenseState } from './license_state'; -import { licensingMock } from '../../../../plugins/licensing/server/mocks'; +import { licensingMock } from '../../../licensing/server/mocks'; describe('license_state', () => { const getRawLicense = jest.fn(); diff --git a/x-pack/plugins/alerting/server/lib/license_state.ts b/x-pack/plugins/alerts/server/lib/license_state.ts similarity index 90% rename from x-pack/plugins/alerting/server/lib/license_state.ts rename to x-pack/plugins/alerts/server/lib/license_state.ts index 211d7a75dc4fa..ea0106f717b02 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.ts +++ b/x-pack/plugins/alerts/server/lib/license_state.ts @@ -7,7 +7,7 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; import { Observable, Subscription } from 'rxjs'; -import { ILicense } from '../../../../plugins/licensing/common/types'; +import { ILicense } from '../../../licensing/common/types'; import { assertNever } from '../../../../../src/core/server'; import { PLUGIN } from '../constants/plugin'; @@ -43,10 +43,10 @@ export class LicenseState { showAppLink: true, enableAppLink: false, message: i18n.translate( - 'xpack.alerting.serverSideErrors.unavailableLicenseInformationErrorMessage', + 'xpack.alerts.serverSideErrors.unavailableLicenseInformationErrorMessage', { defaultMessage: - 'Alerting is unavailable - license information is not available at this time.', + 'Alerts is unavailable - license information is not available at this time.', } ), }; diff --git a/x-pack/plugins/alerting/server/lib/result_type.ts b/x-pack/plugins/alerts/server/lib/result_type.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/result_type.ts rename to x-pack/plugins/alerts/server/lib/result_type.ts diff --git a/x-pack/plugins/alerting/server/lib/types.test.ts b/x-pack/plugins/alerts/server/lib/types.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/types.test.ts rename to x-pack/plugins/alerts/server/lib/types.test.ts diff --git a/x-pack/plugins/alerting/server/lib/types.ts b/x-pack/plugins/alerts/server/lib/types.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/types.ts rename to x-pack/plugins/alerts/server/lib/types.ts diff --git a/x-pack/plugins/alerting/server/lib/validate_alert_type_params.test.ts b/x-pack/plugins/alerts/server/lib/validate_alert_type_params.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/validate_alert_type_params.test.ts rename to x-pack/plugins/alerts/server/lib/validate_alert_type_params.test.ts diff --git a/x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts b/x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts similarity index 100% rename from x-pack/plugins/alerting/server/lib/validate_alert_type_params.ts rename to x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts diff --git a/x-pack/plugins/alerting/server/mocks.ts b/x-pack/plugins/alerts/server/mocks.ts similarity index 100% rename from x-pack/plugins/alerting/server/mocks.ts rename to x-pack/plugins/alerts/server/mocks.ts diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts similarity index 97% rename from x-pack/plugins/alerting/server/plugin.test.ts rename to x-pack/plugins/alerts/server/plugin.test.ts index 0411899290ab2..008a9bb804c5b 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -6,8 +6,8 @@ import { AlertingPlugin, AlertingPluginsSetup, AlertingPluginsStart } from './plugin'; import { coreMock } from '../../../../src/core/server/mocks'; -import { licensingMock } from '../../../plugins/licensing/server/mocks'; -import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; +import { licensingMock } from '../../licensing/server/mocks'; +import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; import { eventLogServiceMock } from '../../event_log/server/event_log_service.mock'; import { KibanaRequest, CoreSetup } from 'kibana/server'; diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts similarity index 99% rename from x-pack/plugins/alerting/server/plugin.ts rename to x-pack/plugins/alerts/server/plugin.ts index e789e655774a0..324bc9fbfb72b 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -53,7 +53,7 @@ import { LicensingPluginSetup } from '../../licensing/server'; import { PluginSetupContract as ActionsPluginSetupContract, PluginStartContract as ActionsPluginStartContract, -} from '../../../plugins/actions/server'; +} from '../../actions/server'; import { Services } from './types'; import { registerAlertsUsageCollector } from './usage'; import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task'; diff --git a/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts similarity index 100% rename from x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts rename to x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts diff --git a/x-pack/plugins/alerting/server/routes/create.test.ts b/x-pack/plugins/alerts/server/routes/create.test.ts similarity index 98% rename from x-pack/plugins/alerting/server/routes/create.test.ts rename to x-pack/plugins/alerts/server/routes/create.test.ts index a4910495c8a40..9e941903eeaed 100644 --- a/x-pack/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/plugins/alerts/server/routes/create.test.ts @@ -74,7 +74,7 @@ describe('createAlertRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/create.ts b/x-pack/plugins/alerts/server/routes/create.ts similarity index 98% rename from x-pack/plugins/alerting/server/routes/create.ts rename to x-pack/plugins/alerts/server/routes/create.ts index cc3b7d48162e3..6238fca024e55 100644 --- a/x-pack/plugins/alerting/server/routes/create.ts +++ b/x-pack/plugins/alerts/server/routes/create.ts @@ -43,7 +43,7 @@ export const bodySchema = schema.object({ export const createAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: BASE_ALERT_API_PATH, + path: `${BASE_ALERT_API_PATH}/alert`, validate: { body: bodySchema, }, diff --git a/x-pack/plugins/alerting/server/routes/delete.test.ts b/x-pack/plugins/alerts/server/routes/delete.test.ts similarity index 97% rename from x-pack/plugins/alerting/server/routes/delete.test.ts rename to x-pack/plugins/alerts/server/routes/delete.test.ts index 416628d015b5a..9ba4e20312e17 100644 --- a/x-pack/plugins/alerting/server/routes/delete.test.ts +++ b/x-pack/plugins/alerts/server/routes/delete.test.ts @@ -29,7 +29,7 @@ describe('deleteAlertRoute', () => { const [config, handler] = router.delete.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/delete.ts b/x-pack/plugins/alerts/server/routes/delete.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/delete.ts rename to x-pack/plugins/alerts/server/routes/delete.ts index f5a7add632edc..2034bd21fbed6 100644 --- a/x-pack/plugins/alerting/server/routes/delete.ts +++ b/x-pack/plugins/alerts/server/routes/delete.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const deleteAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.delete( { - path: `${BASE_ALERT_API_PATH}/{id}`, + path: `${BASE_ALERT_API_PATH}/alert/{id}`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/disable.test.ts b/x-pack/plugins/alerts/server/routes/disable.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/disable.test.ts rename to x-pack/plugins/alerts/server/routes/disable.test.ts index fde095e9145b6..a82d09854a604 100644 --- a/x-pack/plugins/alerting/server/routes/disable.test.ts +++ b/x-pack/plugins/alerts/server/routes/disable.test.ts @@ -29,7 +29,7 @@ describe('disableAlertRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/_disable"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_disable"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/disable.ts b/x-pack/plugins/alerts/server/routes/disable.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/disable.ts rename to x-pack/plugins/alerts/server/routes/disable.ts index e1eb089cf4e85..dfc5dfbdd5aa2 100644 --- a/x-pack/plugins/alerting/server/routes/disable.ts +++ b/x-pack/plugins/alerts/server/routes/disable.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const disableAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{id}/_disable`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_disable`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/enable.test.ts b/x-pack/plugins/alerts/server/routes/enable.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/enable.test.ts rename to x-pack/plugins/alerts/server/routes/enable.test.ts index e4e89e3f06380..4ee3a12a59dc7 100644 --- a/x-pack/plugins/alerting/server/routes/enable.test.ts +++ b/x-pack/plugins/alerts/server/routes/enable.test.ts @@ -28,7 +28,7 @@ describe('enableAlertRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/_enable"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_enable"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/enable.ts b/x-pack/plugins/alerts/server/routes/enable.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/enable.ts rename to x-pack/plugins/alerts/server/routes/enable.ts index 90e8f552898d9..b6f86b97d6a3a 100644 --- a/x-pack/plugins/alerting/server/routes/enable.ts +++ b/x-pack/plugins/alerts/server/routes/enable.ts @@ -24,7 +24,7 @@ const paramSchema = schema.object({ export const enableAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{id}/_enable`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_enable`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/find.test.ts b/x-pack/plugins/alerts/server/routes/find.test.ts similarity index 93% rename from x-pack/plugins/alerting/server/routes/find.test.ts rename to x-pack/plugins/alerts/server/routes/find.test.ts index cc601bd42b8ca..f20ee0a54dcd9 100644 --- a/x-pack/plugins/alerting/server/routes/find.test.ts +++ b/x-pack/plugins/alerts/server/routes/find.test.ts @@ -30,7 +30,7 @@ describe('findAlertRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/_find"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_find"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ @@ -76,13 +76,8 @@ describe('findAlertRoute', () => { Object { "options": Object { "defaultSearchOperator": "OR", - "fields": undefined, - "filter": undefined, "page": 1, "perPage": 1, - "search": undefined, - "sortField": undefined, - "sortOrder": undefined, }, }, ] diff --git a/x-pack/plugins/alerting/server/routes/find.ts b/x-pack/plugins/alerts/server/routes/find.ts similarity index 81% rename from x-pack/plugins/alerting/server/routes/find.ts rename to x-pack/plugins/alerts/server/routes/find.ts index 3de95c9580cd4..80c9c20eec7da 100644 --- a/x-pack/plugins/alerting/server/routes/find.ts +++ b/x-pack/plugins/alerts/server/routes/find.ts @@ -12,10 +12,11 @@ import { IKibanaResponse, KibanaResponseFactory, } from 'kibana/server'; -import { FindOptions } from '../../../alerting/server'; import { LicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; +import { renameKeys } from './lib/rename_keys'; +import { FindOptions } from '..'; // config definition const querySchema = schema.object({ @@ -63,31 +64,29 @@ export const findAlertRoute = (router: IRouter, licenseState: LicenseState) => { return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); } const alertsClient = context.alerting.getAlertsClient(); + const query = req.query; - const options: FindOptions['options'] = { - perPage: query.per_page, - page: query.page, - search: query.search, - defaultSearchOperator: query.default_search_operator, - sortField: query.sort_field, - fields: query.fields, - filter: query.filter, - sortOrder: query.sort_order, + const renameMap = { + default_search_operator: 'defaultSearchOperator', + fields: 'fields', + has_reference: 'hasReference', + page: 'page', + per_page: 'perPage', + search: 'search', + sort_field: 'sortField', + sort_order: 'sortOrder', + filter: 'filter', }; + const options = renameKeys>(renameMap, query); + if (query.search_fields) { options.searchFields = Array.isArray(query.search_fields) ? query.search_fields : [query.search_fields]; } - if (query.has_reference) { - options.hasReference = query.has_reference; - } - - const findResult = await alertsClient.find({ - options, - }); + const findResult = await alertsClient.find({ options }); return res.ok({ body: findResult, }); diff --git a/x-pack/plugins/alerting/server/routes/get.test.ts b/x-pack/plugins/alerts/server/routes/get.test.ts similarity index 97% rename from x-pack/plugins/alerting/server/routes/get.test.ts rename to x-pack/plugins/alerts/server/routes/get.test.ts index 7335f13c85a4d..b11224ff4794e 100644 --- a/x-pack/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/plugins/alerts/server/routes/get.test.ts @@ -60,7 +60,7 @@ describe('getAlertRoute', () => { getAlertRoute(router, licenseState); const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/get.ts b/x-pack/plugins/alerts/server/routes/get.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/get.ts rename to x-pack/plugins/alerts/server/routes/get.ts index cd78e7fbacddb..ae9ebe1299371 100644 --- a/x-pack/plugins/alerting/server/routes/get.ts +++ b/x-pack/plugins/alerts/server/routes/get.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const getAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/{id}`, + path: `${BASE_ALERT_API_PATH}/alert/{id}`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/get_alert_state.test.ts b/x-pack/plugins/alerts/server/routes/get_alert_state.test.ts similarity index 94% rename from x-pack/plugins/alerting/server/routes/get_alert_state.test.ts rename to x-pack/plugins/alerts/server/routes/get_alert_state.test.ts index 20a420ca00986..8c9051093f85b 100644 --- a/x-pack/plugins/alerting/server/routes/get_alert_state.test.ts +++ b/x-pack/plugins/alerts/server/routes/get_alert_state.test.ts @@ -47,7 +47,7 @@ describe('getAlertStateRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/state"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/state"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ @@ -90,7 +90,7 @@ describe('getAlertStateRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/state"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/state"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ @@ -133,7 +133,7 @@ describe('getAlertStateRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/state"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/state"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/get_alert_state.ts b/x-pack/plugins/alerts/server/routes/get_alert_state.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/get_alert_state.ts rename to x-pack/plugins/alerts/server/routes/get_alert_state.ts index a5cb14154db67..b27ae3758e1b9 100644 --- a/x-pack/plugins/alerting/server/routes/get_alert_state.ts +++ b/x-pack/plugins/alerts/server/routes/get_alert_state.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const getAlertStateRoute = (router: IRouter, licenseState: LicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/{id}/state`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/state`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/health.test.ts b/x-pack/plugins/alerts/server/routes/health.test.ts similarity index 99% rename from x-pack/plugins/alerting/server/routes/health.test.ts rename to x-pack/plugins/alerts/server/routes/health.test.ts index 9b1c95c393f56..b3f41e03ebdc9 100644 --- a/x-pack/plugins/alerting/server/routes/health.test.ts +++ b/x-pack/plugins/alerts/server/routes/health.test.ts @@ -31,7 +31,7 @@ describe('healthRoute', () => { const [config] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/_health"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_health"`); }); it('queries the usage api', async () => { diff --git a/x-pack/plugins/alerting/server/routes/health.ts b/x-pack/plugins/alerts/server/routes/health.ts similarity index 98% rename from x-pack/plugins/alerting/server/routes/health.ts rename to x-pack/plugins/alerts/server/routes/health.ts index fb4c5e76a02c9..b66e28b24e8a7 100644 --- a/x-pack/plugins/alerting/server/routes/health.ts +++ b/x-pack/plugins/alerts/server/routes/health.ts @@ -34,7 +34,7 @@ export function healthRoute( ) { router.get( { - path: '/api/alert/_health', + path: '/api/alerts/_health', validate: false, }, router.handleLegacyErrors(async function ( diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerts/server/routes/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/routes/index.ts rename to x-pack/plugins/alerts/server/routes/index.ts diff --git a/x-pack/plugins/alerting/server/routes/lib/error_handler.ts b/x-pack/plugins/alerts/server/routes/lib/error_handler.ts similarity index 94% rename from x-pack/plugins/alerting/server/routes/lib/error_handler.ts rename to x-pack/plugins/alerts/server/routes/lib/error_handler.ts index b3cf48c52fe17..e0c620b0670c9 100644 --- a/x-pack/plugins/alerting/server/routes/lib/error_handler.ts +++ b/x-pack/plugins/alerts/server/routes/lib/error_handler.ts @@ -27,7 +27,7 @@ export function handleDisabledApiKeysError( if (isApiKeyDisabledError(e)) { return response.badRequest({ body: new Error( - i18n.translate('xpack.alerting.api.error.disabledApiKeys', { + i18n.translate('xpack.alerts.api.error.disabledApiKeys', { defaultMessage: 'Alerting relies upon API keys which appear to be disabled', }) ), diff --git a/x-pack/plugins/alerts/server/routes/lib/rename_keys.ts b/x-pack/plugins/alerts/server/routes/lib/rename_keys.ts new file mode 100644 index 0000000000000..bfe60a0ecc648 --- /dev/null +++ b/x-pack/plugins/alerts/server/routes/lib/rename_keys.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const renameKeys = , U extends Record>( + keysMap: Record, + obj: Record +): T => + Object.keys(obj).reduce((acc, key) => { + return { + ...acc, + ...{ [keysMap[key] || key]: obj[key] }, + }; + }, {} as T); diff --git a/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts b/x-pack/plugins/alerts/server/routes/list_alert_types.test.ts similarity index 94% rename from x-pack/plugins/alerting/server/routes/list_alert_types.test.ts rename to x-pack/plugins/alerts/server/routes/list_alert_types.test.ts index e940b2d102045..3192154f6664c 100644 --- a/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts +++ b/x-pack/plugins/alerts/server/routes/list_alert_types.test.ts @@ -27,7 +27,7 @@ describe('listAlertTypesRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/types"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/list_alert_types"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ @@ -89,7 +89,7 @@ describe('listAlertTypesRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/types"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/list_alert_types"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ @@ -140,7 +140,7 @@ describe('listAlertTypesRoute', () => { const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/types"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/list_alert_types"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/list_alert_types.ts b/x-pack/plugins/alerts/server/routes/list_alert_types.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/list_alert_types.ts rename to x-pack/plugins/alerts/server/routes/list_alert_types.ts index f5b4e3263f341..51a4558108e29 100644 --- a/x-pack/plugins/alerting/server/routes/list_alert_types.ts +++ b/x-pack/plugins/alerts/server/routes/list_alert_types.ts @@ -18,7 +18,7 @@ import { BASE_ALERT_API_PATH } from '../../common'; export const listAlertTypesRoute = (router: IRouter, licenseState: LicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/types`, + path: `${BASE_ALERT_API_PATH}/list_alert_types`, validate: {}, options: { tags: ['access:alerting-read'], diff --git a/x-pack/plugins/alerting/server/routes/mute_all.test.ts b/x-pack/plugins/alerts/server/routes/mute_all.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/mute_all.test.ts rename to x-pack/plugins/alerts/server/routes/mute_all.test.ts index 5ef9e3694f8f4..bcdb8cbd022ac 100644 --- a/x-pack/plugins/alerting/server/routes/mute_all.test.ts +++ b/x-pack/plugins/alerts/server/routes/mute_all.test.ts @@ -28,7 +28,7 @@ describe('muteAllAlertRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/_mute_all"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_mute_all"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/mute_all.ts b/x-pack/plugins/alerts/server/routes/mute_all.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/mute_all.ts rename to x-pack/plugins/alerts/server/routes/mute_all.ts index b43a1ec30ed1f..5b05d7231c385 100644 --- a/x-pack/plugins/alerting/server/routes/mute_all.ts +++ b/x-pack/plugins/alerts/server/routes/mute_all.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const muteAllAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{id}/_mute_all`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_mute_all`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/mute_instance.test.ts b/x-pack/plugins/alerts/server/routes/mute_instance.test.ts similarity index 92% rename from x-pack/plugins/alerting/server/routes/mute_instance.test.ts rename to x-pack/plugins/alerts/server/routes/mute_instance.test.ts index 2e6adedb76df9..c382c12de21cd 100644 --- a/x-pack/plugins/alerting/server/routes/mute_instance.test.ts +++ b/x-pack/plugins/alerts/server/routes/mute_instance.test.ts @@ -29,7 +29,7 @@ describe('muteAlertInstanceRoute', () => { const [config, handler] = router.post.mock.calls[0]; expect(config.path).toMatchInlineSnapshot( - `"/api/alert/{alertId}/alert_instance/{alertInstanceId}/_mute"` + `"/api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute"` ); expect(config.options).toMatchInlineSnapshot(` Object { @@ -45,8 +45,8 @@ describe('muteAlertInstanceRoute', () => { { alertsClient }, { params: { - alertId: '1', - alertInstanceId: '2', + alert_id: '1', + alert_instance_id: '2', }, }, ['noContent'] diff --git a/x-pack/plugins/alerting/server/routes/mute_instance.ts b/x-pack/plugins/alerts/server/routes/mute_instance.ts similarity index 72% rename from x-pack/plugins/alerting/server/routes/mute_instance.ts rename to x-pack/plugins/alerts/server/routes/mute_instance.ts index c0c69fe9653da..00550f4af3418 100644 --- a/x-pack/plugins/alerting/server/routes/mute_instance.ts +++ b/x-pack/plugins/alerts/server/routes/mute_instance.ts @@ -15,16 +15,18 @@ import { import { LicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { BASE_ALERT_API_PATH } from '../../common'; +import { renameKeys } from './lib/rename_keys'; +import { MuteOptions } from '../alerts_client'; const paramSchema = schema.object({ - alertId: schema.string(), - alertInstanceId: schema.string(), + alert_id: schema.string(), + alert_instance_id: schema.string(), }); export const muteAlertInstanceRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{alertId}/alert_instance/{alertInstanceId}/_mute`, + path: `${BASE_ALERT_API_PATH}/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`, validate: { params: paramSchema, }, @@ -42,8 +44,14 @@ export const muteAlertInstanceRoute = (router: IRouter, licenseState: LicenseSta return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); } const alertsClient = context.alerting.getAlertsClient(); - const { alertId, alertInstanceId } = req.params; - await alertsClient.muteInstance({ alertId, alertInstanceId }); + + const renameMap = { + alert_id: 'alertId', + alert_instance_id: 'alertInstanceId', + }; + + const renamedQuery = renameKeys>(renameMap, req.params); + await alertsClient.muteInstance(renamedQuery); return res.noContent(); }) ); diff --git a/x-pack/plugins/alerting/server/routes/unmute_all.test.ts b/x-pack/plugins/alerts/server/routes/unmute_all.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/unmute_all.test.ts rename to x-pack/plugins/alerts/server/routes/unmute_all.test.ts index 1756dbd3fb41d..e13af38fe4cb1 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_all.test.ts +++ b/x-pack/plugins/alerts/server/routes/unmute_all.test.ts @@ -27,7 +27,7 @@ describe('unmuteAllAlertRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/_unmute_all"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_unmute_all"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/unmute_all.ts b/x-pack/plugins/alerts/server/routes/unmute_all.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/unmute_all.ts rename to x-pack/plugins/alerts/server/routes/unmute_all.ts index d4b6e8b7d61b1..1efc9ed40054e 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_all.ts +++ b/x-pack/plugins/alerts/server/routes/unmute_all.ts @@ -23,7 +23,7 @@ const paramSchema = schema.object({ export const unmuteAllAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{id}/_unmute_all`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_unmute_all`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/unmute_instance.test.ts b/x-pack/plugins/alerts/server/routes/unmute_instance.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/unmute_instance.test.ts rename to x-pack/plugins/alerts/server/routes/unmute_instance.test.ts index 9b9542c606741..b2e2f24e91de9 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_instance.test.ts +++ b/x-pack/plugins/alerts/server/routes/unmute_instance.test.ts @@ -29,7 +29,7 @@ describe('unmuteAlertInstanceRoute', () => { const [config, handler] = router.post.mock.calls[0]; expect(config.path).toMatchInlineSnapshot( - `"/api/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute"` + `"/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute"` ); expect(config.options).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/alerting/server/routes/unmute_instance.ts b/x-pack/plugins/alerts/server/routes/unmute_instance.ts similarity index 94% rename from x-pack/plugins/alerting/server/routes/unmute_instance.ts rename to x-pack/plugins/alerts/server/routes/unmute_instance.ts index 97ccd8f0adce7..967f9f890c9fb 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_instance.ts +++ b/x-pack/plugins/alerts/server/routes/unmute_instance.ts @@ -24,7 +24,7 @@ const paramSchema = schema.object({ export const unmuteAlertInstanceRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{alertId}/alert_instance/{alertInstanceId}/_unmute`, + path: `${BASE_ALERT_API_PATH}/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/update.test.ts b/x-pack/plugins/alerts/server/routes/update.test.ts similarity index 98% rename from x-pack/plugins/alerting/server/routes/update.test.ts rename to x-pack/plugins/alerts/server/routes/update.test.ts index cd96f289b8714..c7d23f2670b45 100644 --- a/x-pack/plugins/alerting/server/routes/update.test.ts +++ b/x-pack/plugins/alerts/server/routes/update.test.ts @@ -51,7 +51,7 @@ describe('updateAlertRoute', () => { const [config, handler] = router.put.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/update.ts b/x-pack/plugins/alerts/server/routes/update.ts similarity index 98% rename from x-pack/plugins/alerting/server/routes/update.ts rename to x-pack/plugins/alerts/server/routes/update.ts index 23fea7dc4002f..99b81dfc5b56e 100644 --- a/x-pack/plugins/alerting/server/routes/update.ts +++ b/x-pack/plugins/alerts/server/routes/update.ts @@ -44,7 +44,7 @@ const bodySchema = schema.object({ export const updateAlertRoute = (router: IRouter, licenseState: LicenseState) => { router.put( { - path: `${BASE_ALERT_API_PATH}/{id}`, + path: `${BASE_ALERT_API_PATH}/alert/{id}`, validate: { body: bodySchema, params: paramSchema, diff --git a/x-pack/plugins/alerting/server/routes/update_api_key.test.ts b/x-pack/plugins/alerts/server/routes/update_api_key.test.ts similarity index 95% rename from x-pack/plugins/alerting/server/routes/update_api_key.test.ts rename to x-pack/plugins/alerts/server/routes/update_api_key.test.ts index 0347feb24a235..babae59553b5b 100644 --- a/x-pack/plugins/alerting/server/routes/update_api_key.test.ts +++ b/x-pack/plugins/alerts/server/routes/update_api_key.test.ts @@ -28,7 +28,7 @@ describe('updateApiKeyRoute', () => { const [config, handler] = router.post.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alert/{id}/_update_api_key"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_update_api_key"`); expect(config.options).toMatchInlineSnapshot(` Object { "tags": Array [ diff --git a/x-pack/plugins/alerting/server/routes/update_api_key.ts b/x-pack/plugins/alerts/server/routes/update_api_key.ts similarity index 96% rename from x-pack/plugins/alerting/server/routes/update_api_key.ts rename to x-pack/plugins/alerts/server/routes/update_api_key.ts index 9d88201d7cd43..4736351a25cbd 100644 --- a/x-pack/plugins/alerting/server/routes/update_api_key.ts +++ b/x-pack/plugins/alerts/server/routes/update_api_key.ts @@ -24,7 +24,7 @@ const paramSchema = schema.object({ export const updateApiKeyRoute = (router: IRouter, licenseState: LicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/{id}/_update_api_key`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_update_api_key`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerts/server/saved_objects/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/saved_objects/index.ts rename to x-pack/plugins/alerts/server/saved_objects/index.ts diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.json b/x-pack/plugins/alerts/server/saved_objects/mappings.json similarity index 100% rename from x-pack/plugins/alerting/server/saved_objects/mappings.json rename to x-pack/plugins/alerts/server/saved_objects/mappings.json diff --git a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts similarity index 98% rename from x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts rename to x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts index 28b3576dffc6e..efac4c5dcdc01 100644 --- a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ConcreteTaskInstance, TaskStatus } from '../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { AlertTaskInstance, taskInstanceToAlertTaskInstance } from './alert_task_instance'; import uuid from 'uuid'; import { SanitizedAlert } from '../types'; diff --git a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.ts similarity index 95% rename from x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts rename to x-pack/plugins/alerts/server/task_runner/alert_task_instance.ts index 4be506b78493b..a290f3fa33c70 100644 --- a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.ts +++ b/x-pack/plugins/alerts/server/task_runner/alert_task_instance.ts @@ -6,7 +6,7 @@ import * as t from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; -import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance } from '../../../task_manager/server'; import { SanitizedAlert, AlertTaskState, diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts rename to x-pack/plugins/alerts/server/task_runner/create_execution_handler.test.ts diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts similarity index 98% rename from x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts rename to x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts index 61bbab50b1222..3c58c6d9ba288 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerts/server/task_runner/create_execution_handler.ts @@ -8,7 +8,7 @@ import { pluck } from 'lodash'; import { AlertAction, State, Context, AlertType } from '../types'; import { Logger } from '../../../../../src/core/server'; import { transformActionParams } from './transform_action_params'; -import { PluginStartContract as ActionsPluginStartContract } from '../../../../plugins/actions/server'; +import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server'; import { IEventLogger, IEvent, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server'; import { EVENT_LOG_ACTIONS } from '../plugin'; diff --git a/x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts b/x-pack/plugins/alerts/server/task_runner/get_next_run_at.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/get_next_run_at.test.ts rename to x-pack/plugins/alerts/server/task_runner/get_next_run_at.test.ts diff --git a/x-pack/plugins/alerting/server/task_runner/get_next_run_at.ts b/x-pack/plugins/alerts/server/task_runner/get_next_run_at.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/get_next_run_at.ts rename to x-pack/plugins/alerts/server/task_runner/get_next_run_at.ts diff --git a/x-pack/plugins/alerting/server/task_runner/index.ts b/x-pack/plugins/alerts/server/task_runner/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/index.ts rename to x-pack/plugins/alerts/server/task_runner/index.ts diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts similarity index 99% rename from x-pack/plugins/alerting/server/task_runner/task_runner.test.ts rename to x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 98824b8cf4e1a..983dff86d5602 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -7,10 +7,10 @@ import sinon from 'sinon'; import { schema } from '@kbn/config-schema'; import { AlertExecutorOptions } from '../types'; -import { ConcreteTaskInstance, TaskStatus } from '../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { TaskRunnerContext } from './task_runner_factory'; import { TaskRunner } from './task_runner'; -import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks'; +import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; import { loggingServiceMock } from '../../../../../src/core/server/mocks'; import { PluginStartContract as ActionsPluginStart } from '../../../actions/server'; import { actionsMock } from '../../../actions/server/mocks'; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts similarity index 99% rename from x-pack/plugins/alerting/server/task_runner/task_runner.ts rename to x-pack/plugins/alerts/server/task_runner/task_runner.ts index 0831163d1d326..be399893088e3 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -7,7 +7,7 @@ import { pick, mapValues, omit, without } from 'lodash'; import { Logger, SavedObject, KibanaRequest } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; -import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance } from '../../../task_manager/server'; import { createExecutionHandler } from './create_execution_handler'; import { AlertInstance, createAlertInstanceFactory } from '../alert_instance'; import { getNextRunAt } from './get_next_run_at'; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts similarity index 93% rename from x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts rename to x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts index c1318bac48dfb..7d9710d8a3e08 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.test.ts @@ -5,9 +5,9 @@ */ import sinon from 'sinon'; -import { ConcreteTaskInstance, TaskStatus } from '../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; -import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks'; +import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; import { loggingServiceMock } from '../../../../../src/core/server/mocks'; import { actionsMock } from '../../../actions/server/mocks'; import { alertsMock } from '../mocks'; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts similarity index 87% rename from x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts rename to x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts index c50e288d2e520..ca762cf2b2105 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { Logger } from '../../../../../src/core/server'; -import { RunContext } from '../../../../plugins/task_manager/server'; -import { EncryptedSavedObjectsClient } from '../../../../plugins/encrypted_saved_objects/server'; -import { PluginStartContract as ActionsPluginStartContract } from '../../../../plugins/actions/server'; +import { RunContext } from '../../../task_manager/server'; +import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; +import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server'; import { AlertType, GetBasePathFunction, diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts b/x-pack/plugins/alerts/server/task_runner/transform_action_params.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/transform_action_params.test.ts rename to x-pack/plugins/alerts/server/task_runner/transform_action_params.test.ts diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerts/server/task_runner/transform_action_params.ts similarity index 100% rename from x-pack/plugins/alerting/server/task_runner/transform_action_params.ts rename to x-pack/plugins/alerts/server/task_runner/transform_action_params.ts diff --git a/x-pack/plugins/alerting/server/test_utils/index.ts b/x-pack/plugins/alerts/server/test_utils/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/test_utils/index.ts rename to x-pack/plugins/alerts/server/test_utils/index.ts diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerts/server/types.ts similarity index 100% rename from x-pack/plugins/alerting/server/types.ts rename to x-pack/plugins/alerts/server/types.ts diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts b/x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts rename to x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerts/server/usage/alerts_telemetry.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/alerts_telemetry.ts rename to x-pack/plugins/alerts/server/usage/alerts_telemetry.ts diff --git a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.test.ts b/x-pack/plugins/alerts/server/usage/alerts_usage_collector.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/alerts_usage_collector.test.ts rename to x-pack/plugins/alerts/server/usage/alerts_usage_collector.test.ts diff --git a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts b/x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts rename to x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts diff --git a/x-pack/plugins/alerting/server/usage/index.ts b/x-pack/plugins/alerts/server/usage/index.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/index.ts rename to x-pack/plugins/alerts/server/usage/index.ts diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerts/server/usage/task.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/task.ts rename to x-pack/plugins/alerts/server/usage/task.ts diff --git a/x-pack/plugins/alerting/server/usage/types.ts b/x-pack/plugins/alerts/server/usage/types.ts similarity index 100% rename from x-pack/plugins/alerting/server/usage/types.ts rename to x-pack/plugins/alerts/server/usage/types.ts diff --git a/x-pack/plugins/apm/common/service_map.ts b/x-pack/plugins/apm/common/service_map.ts index 9f20005c2b189..43f3585d0ebb2 100644 --- a/x-pack/plugins/apm/common/service_map.ts +++ b/x-pack/plugins/apm/common/service_map.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import cytoscape from 'cytoscape'; -import { ILicense } from '../../licensing/public'; +import { ILicense } from '../../licensing/common/types'; import { AGENT_NAME, SERVICE_ENVIRONMENT, diff --git a/x-pack/plugins/apm/dev_docs/vscode_setup.md b/x-pack/plugins/apm/dev_docs/vscode_setup.md index 1c80d1476520d..c7adad4fd0942 100644 --- a/x-pack/plugins/apm/dev_docs/vscode_setup.md +++ b/x-pack/plugins/apm/dev_docs/vscode_setup.md @@ -1,8 +1,8 @@ -### Visual Studio Code +# Visual Studio Code When using [Visual Studio Code](https://code.visualstudio.com/) with APM it's best to set up a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) and add the `x-pack/plugins/apm` directory, the `x-pack` directory, and the root of the Kibana repository to the workspace. This makes it so you can navigate and search within APM and use the wider workspace roots when you need to widen your search. -#### Using the Jest extension +## Using the Jest extension The [vscode-jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) is a good way to run your Jest tests inside the editor. @@ -22,31 +22,21 @@ If you have a workspace configured as described above you should have: "jest.disabledWorkspaceFolders": ["kibana", "x-pack"] ``` -in your Workspace settings, and: - -```json -"jest.pathToJest": "node scripts/jest.js --testPathPattern=plugins/apm", -"jest.rootPath": "../../.." -``` - -in the settings for the APM folder. - -#### Jest debugging +## Jest debugging To make the [VSCode debugger](https://vscode.readthedocs.io/en/latest/editor/debugging/) work with Jest (you can set breakpoints in the code and tests and use the VSCode debugger) you'll need the [Node Debug extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.node-debug2) installed and can set up a launch configuration like: ```json { "type": "node", - "name": "APM Jest", + "name": "vscode-jest-tests", "request": "launch", - "args": ["--runInBand", "--testPathPattern=plugins/apm"], - "cwd": "${workspaceFolder}/../../..", - "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart", + "args": ["--runInBand"], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", "disableOptimisticBPs": true, - "program": "${workspaceFolder}/../../../scripts/jest.js", - "runtimeVersion": "10.15.2" + "program": "${workspaceFolder}/../../../node_modules/jest/bin/jest" } ``` diff --git a/x-pack/plugins/apm/jest.config.js b/x-pack/plugins/apm/jest.config.js new file mode 100644 index 0000000000000..c3ae694fe8e14 --- /dev/null +++ b/x-pack/plugins/apm/jest.config.js @@ -0,0 +1,44 @@ +/* + * 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. + */ + +// This is an APM-specific Jest configuration which overrides the x-pack +// configuration. It's intended for use in development and does not run in CI, +// which runs the entire x-pack suite. Run `npx jest`. + +require('../../../src/setup_node_env'); + +const { createJestConfig } = require('../../dev-tools/jest/create_jest_config'); +const { resolve } = require('path'); + +const rootDir = resolve(__dirname, '.'); +const xPackKibanaDirectory = resolve(__dirname, '../..'); +const kibanaDirectory = resolve(__dirname, '../../..'); + +const jestConfig = createJestConfig({ + kibanaDirectory, + rootDir, + xPackKibanaDirectory, +}); + +module.exports = { + ...jestConfig, + reporters: ['default'], + roots: [`${rootDir}/common`, `${rootDir}/public`, `${rootDir}/server`], + collectCoverage: true, + collectCoverageFrom: [ + '**/*.{js,jsx,ts,tsx}', + '!**/{__test__,__snapshots__,__examples__,integration_tests,tests}/**', + '!**/*.test.{js,ts,tsx}', + '!**/dev_docs/**', + '!**/e2e/**', + '!**/scripts/**', + '!**/target/**', + '!**/typings/**', + '!**/mocks/**', + ], + coverageDirectory: `${rootDir}/target/coverage/jest`, + coverageReporters: ['html'], +}; diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index dd89fac66f6e8..2de3c9c97065d 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -15,7 +15,7 @@ "usageCollection", "taskManager", "actions", - "alerting", + "alerts", "observability", "security" ], diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx index bfb9e99b4fc4c..80d5f739bea5a 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx @@ -82,7 +82,7 @@ export function AlertIntegrations(props: Props) { } ), href: plugin.core.http.basePath.prepend( - '/app/kibana#/management/insightsAndAlerting/triggersActions/alerts' + '/app/management/insightsAndAlerting/triggersActions/alerts' ), icon: 'tableOfContents', }, diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx index bcc31a30b154d..321617ed8496a 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx @@ -92,7 +92,7 @@ export class ServiceIntegrations extends React.Component { ), icon: 'watchesApp', href: core.http.basePath.prepend( - '/app/kibana#/management/insightsAndAlerting/watcher' + '/app/management/insightsAndAlerting/watcher' ), target: '_blank', onClick: () => this.closePopover(), diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx index c3d426a6275a7..0dbde5ea86a18 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx @@ -28,7 +28,7 @@ export function ServiceDetails({ tab }: Props) { const canSaveAlerts = !!plugin.core.application.capabilities.apm[ 'alerting:save' ]; - const isAlertingPluginEnabled = 'alerting' in plugin.plugins; + const isAlertingPluginEnabled = 'alerts' in plugin.plugins; const isAlertingAvailable = isAlertingPluginEnabled && (canReadAlerts || canSaveAlerts); diff --git a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx index 4033d684da981..481e89e09685e 100644 --- a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx +++ b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx @@ -11,7 +11,7 @@ import { useApmPluginContext } from '../../hooks/useApmPluginContext'; export function InvalidLicenseNotification() { const { core } = useApmPluginContext(); const manageLicenseURL = core.http.basePath.prepend( - '/app/kibana#/management/stack/license_management' + '/app/management/stack/license_management' ); return ( diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index e732e695b36b1..76320efe617ea 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -17,7 +17,7 @@ import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { PluginSetupContract as AlertingPluginPublicSetup, PluginStartContract as AlertingPluginPublicStart, -} from '../../alerting/public'; +} from '../../alerts/public'; import { FeaturesPluginSetup } from '../../features/public'; import { DataPublicPluginSetup, @@ -44,7 +44,7 @@ export type ApmPluginSetup = void; export type ApmPluginStart = void; export interface ApmPluginSetupDeps { - alerting?: AlertingPluginPublicSetup; + alerts?: AlertingPluginPublicSetup; data: DataPublicPluginSetup; features: FeaturesPluginSetup; home: HomePublicPluginSetup; @@ -53,7 +53,7 @@ export interface ApmPluginSetupDeps { } export interface ApmPluginStartDeps { - alerting?: AlertingPluginPublicStart; + alerts?: AlertingPluginPublicStart; data: DataPublicPluginStart; home: void; licensing: void; diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md index 62465e920d793..ceed5e6c39716 100644 --- a/x-pack/plugins/apm/readme.md +++ b/x-pack/plugins/apm/readme.md @@ -39,18 +39,26 @@ _Starts Kibana (:5701), APM Server (:8201) and Elasticsearch (:9201). Ingests sa ### Unit testing -Note: Run the following commands from `kibana/x-pack`. +Note: Run the following commands from `kibana/x-pack/plugins/apm`. #### Run unit tests ``` -node scripts/jest.js plugins/apm --watch +npx jest --watch ``` #### Update snapshots ``` -node scripts/jest.js plugins/apm --updateSnapshot +npx jest --updateSnapshot +``` + +#### Coverage + +HTML coverage report can be found in target/coverage/jest after tests have run. + +``` +open target/coverage/jest/index.html ``` ### Functional tests diff --git a/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts b/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts index 8af9f386ecebf..4b8e9cf937a2b 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts @@ -5,25 +5,25 @@ */ import { Observable } from 'rxjs'; -import { AlertingPlugin } from '../../../../alerting/server'; +import { AlertingPlugin } from '../../../../alerts/server'; import { ActionsPlugin } from '../../../../actions/server'; import { registerTransactionDurationAlertType } from './register_transaction_duration_alert_type'; import { registerErrorRateAlertType } from './register_error_rate_alert_type'; import { APMConfig } from '../..'; interface Params { - alerting: AlertingPlugin['setup']; + alerts: AlertingPlugin['setup']; actions: ActionsPlugin['setup']; config$: Observable; } export function registerApmAlerts(params: Params) { registerTransactionDurationAlertType({ - alerting: params.alerting, + alerts: params.alerts, config$: params.config$, }); registerErrorRateAlertType({ - alerting: params.alerting, + alerts: params.alerts, config$: params.config$, }); } diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts index ee7bd9eeb4b6f..53843b7f7412b 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts @@ -19,12 +19,12 @@ import { SERVICE_NAME, SERVICE_ENVIRONMENT, } from '../../../common/elasticsearch_fieldnames'; -import { AlertingPlugin } from '../../../../alerting/server'; +import { AlertingPlugin } from '../../../../alerts/server'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { APMConfig } from '../..'; interface RegisterAlertParams { - alerting: AlertingPlugin['setup']; + alerts: AlertingPlugin['setup']; config$: Observable; } @@ -39,10 +39,10 @@ const paramsSchema = schema.object({ const alertTypeConfig = ALERT_TYPES_CONFIG[AlertType.ErrorRate]; export function registerErrorRateAlertType({ - alerting, + alerts, config$, }: RegisterAlertParams) { - alerting.registerType({ + alerts.registerType({ id: AlertType.ErrorRate, name: alertTypeConfig.name, actionGroups: alertTypeConfig.actionGroups, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index afb402200a07b..1fd1aef4c8b70 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -18,12 +18,12 @@ import { TRANSACTION_DURATION, SERVICE_ENVIRONMENT, } from '../../../common/elasticsearch_fieldnames'; -import { AlertingPlugin } from '../../../../alerting/server'; +import { AlertingPlugin } from '../../../../alerts/server'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { APMConfig } from '../..'; interface RegisterAlertParams { - alerting: AlertingPlugin['setup']; + alerts: AlertingPlugin['setup']; config$: Observable; } @@ -44,10 +44,10 @@ const paramsSchema = schema.object({ const alertTypeConfig = ALERT_TYPES_CONFIG[AlertType.TransactionDuration]; export function registerTransactionDurationAlertType({ - alerting, + alerts, config$, }: RegisterAlertParams) { - alerting.registerType({ + alerts.registerType({ id: AlertType.TransactionDuration, name: alertTypeConfig.name, actionGroups: alertTypeConfig.actionGroups, diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index b9ad14f7ec47d..d32d16d4c3cc8 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -17,7 +17,7 @@ import { ObservabilityPluginSetup } from '../../observability/server'; import { SecurityPluginSetup } from '../../security/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; import { TaskManagerSetupContract } from '../../task_manager/server'; -import { AlertingPlugin } from '../../alerting/server'; +import { AlertingPlugin } from '../../alerts/server'; import { ActionsPlugin } from '../../actions/server'; import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server'; import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index'; @@ -57,7 +57,7 @@ export class APMPlugin implements Plugin { cloud?: CloudSetup; usageCollection?: UsageCollectionSetup; taskManager?: TaskManagerSetupContract; - alerting?: AlertingPlugin['setup']; + alerts?: AlertingPlugin['setup']; actions?: ActionsPlugin['setup']; observability?: ObservabilityPluginSetup; features: FeaturesPluginSetup; @@ -73,9 +73,9 @@ export class APMPlugin implements Plugin { core.savedObjects.registerType(apmIndices); core.savedObjects.registerType(apmTelemetry); - if (plugins.actions && plugins.alerting) { + if (plugins.actions && plugins.alerts) { registerApmAlerts({ - alerting: plugins.alerting, + alerts: plugins.alerts, actions: plugins.actions, config$: mergedConfig$, }); diff --git a/x-pack/plugins/apm/typings/numeral.d.ts b/x-pack/plugins/apm/typings/numeral.d.ts deleted file mode 100644 index 2616639cdeff0..0000000000000 --- a/x-pack/plugins/apm/typings/numeral.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -interface Numeral { - (value?: unknown): Numeral; - format: (pattern: string) => string; - unformat: (pattern: string) => number; - language: (key?: any, values?: any) => Numeral; - set: (value?: any) => Numeral; -} - -// eslint-disable-next-line no-var -declare var numeral: Numeral; - -declare module '@elastic/numeral' { - export = numeral; -} diff --git a/x-pack/plugins/beats_management/public/application.tsx b/x-pack/plugins/beats_management/public/application.tsx index 6711e93895b62..0b8b0ad3dafc9 100644 --- a/x-pack/plugins/beats_management/public/application.tsx +++ b/x-pack/plugins/beats_management/public/application.tsx @@ -8,7 +8,7 @@ import * as euiVars from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import React from 'react'; import ReactDOM from 'react-dom'; -import { HashRouter } from 'react-router-dom'; +import { Router } from 'react-router-dom'; import { ThemeProvider } from 'styled-components'; import { Provider as UnstatedProvider, Subscribe } from 'unstated'; import { Background } from './components/layouts/background'; @@ -19,19 +19,13 @@ import { TagsContainer } from './containers/tags'; import { FrontendLibs } from './lib/types'; import { AppRouter } from './router'; import { services } from './kbn_services'; -import { - ManagementAppMountParams, - ManagementSectionId, -} from '../../../../src/plugins/management/public'; +import { ManagementAppMountParams } from '../../../../src/plugins/management/public'; -export const renderApp = ( - { basePath, element, setBreadcrumbs }: ManagementAppMountParams, - libs: FrontendLibs -) => { +export const renderApp = ({ element, history }: ManagementAppMountParams, libs: FrontendLibs) => { ReactDOM.render( - + @@ -48,7 +42,7 @@ export const renderApp = ( - +
, element diff --git a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts index 96884cf4bead8..0574cb9b2dd6d 100644 --- a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts +++ b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts @@ -24,8 +24,7 @@ export const APPS = { }; export const MANAGEMENT_ID = 'cross_cluster_replication'; -export const BASE_PATH = `/management/data/${MANAGEMENT_ID}`; -export const BASE_PATH_REMOTE_CLUSTERS = '/management/data/remote_clusters'; +export const BASE_PATH_REMOTE_CLUSTERS = 'data/remote_clusters'; export const API_BASE_PATH = '/api/cross_cluster_replication'; export const API_REMOTE_CLUSTERS_BASE_PATH = '/api/remote_clusters'; export const API_INDEX_MANAGEMENT_BASE_PATH = '/api/index_management'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js index e240b5b304c33..a4edee7268a23 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js @@ -12,7 +12,11 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js index c32f6a54114c7..ea372d534d817 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js @@ -14,7 +14,11 @@ import { AUTO_FOLLOW_PATTERN_EDIT_NAME } from './constants'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), // The auto-follow pattern id to fetch is read from the router ":id" param // so we first set it in our initial entries initialEntries: [`/${AUTO_FOLLOW_PATTERN_EDIT_NAME}`], diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js index 87cf6a13ba011..550e178a1c733 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js @@ -12,7 +12,18 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + history: { + ...router.history, + parentHistory: { + createHref: () => '', + push: () => {}, + }, + }, + getUrlForApp: () => '', + }), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js index 9778b595111a2..31f3844f57fb5 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js @@ -12,7 +12,11 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js index 993cde86ada22..8fc2811e58cdb 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js @@ -14,7 +14,11 @@ import { FOLLOWER_INDEX_EDIT_NAME } from './constants'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), // The follower index id to fetch is read from the router ":id" param // so we first set it in our initial entries initialEntries: [`/${FOLLOWER_INDEX_EDIT_NAME}`], diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js index 16a4fa9cd3f3e..65be10b9d272e 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js @@ -12,7 +12,17 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + history: { + ...router.history, + parentHistory: { + createHref: () => '', + push: () => {}, + }, + }, + getUrlForApp: () => '', + }), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js index 26a0d86cdcbca..e51c444a6b370 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js @@ -5,7 +5,6 @@ */ import { registerTestBed } from '../../../../../../test_utils'; -import { BASE_PATH } from '../../../../common/constants'; import { CrossClusterReplicationHome } from '../../../app/sections/home/home'; import { ccrStore } from '../../../app/store'; import { routing } from '../../../app/services/routing'; @@ -13,8 +12,8 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - initialEntries: [`${BASE_PATH}/follower_indices`], - componentRoutePath: `${BASE_PATH}/:section`, + initialEntries: [`/follower_indices`], + componentRoutePath: `/:section`, onRouter: (router) => (routing.reactRouter = router), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/app.tsx b/x-pack/plugins/cross_cluster_replication/public/app/app.tsx index ec349ccd6f2c7..288da20c353d2 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/app.tsx +++ b/x-pack/plugins/cross_cluster_replication/public/app/app.tsx @@ -5,8 +5,8 @@ */ import React, { Component, Fragment } from 'react'; -import { Route, Switch, Redirect, withRouter, RouteComponentProps } from 'react-router-dom'; -import { History } from 'history'; +import { Route, Switch, Router, Redirect } from 'react-router-dom'; +import { ScopedHistory, ApplicationStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -20,7 +20,6 @@ import { EuiTitle, } from '@elastic/eui'; -import { BASE_PATH } from '../../common/constants'; import { getFatalErrors } from './services/notifications'; import { SectionError } from './components'; import { routing } from './services/routing'; @@ -37,8 +36,8 @@ import { } from './sections'; interface AppProps { - history: History; - location: any; + history: ScopedHistory; + getUrlForApp: ApplicationStart['getUrlForApp']; } interface AppState { @@ -48,7 +47,7 @@ interface AppState { missingClusterPrivileges: any[]; } -class AppComponent extends Component { +class AppComponent extends Component { constructor(props: any) { super(props); this.registerRouter(); @@ -99,12 +98,13 @@ class AppComponent extends Component { } registerRouter() { - const { history, location } = this.props; + const { history, getUrlForApp } = this.props; routing.reactRouter = { history, route: { - location, + location: history.location, }, + getUrlForApp, }; } @@ -189,30 +189,18 @@ class AppComponent extends Component { } return ( -
+ - - - - - - + + + + + + -
+ ); } } -export const App = withRouter(AppComponent); +export const App = AppComponent; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx index 5474708f313c8..1d8f8bacc8c84 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx @@ -99,7 +99,7 @@ const AutoFollowPatternActionMenuUI: FunctionComponent = ({ }), icon: , onClick: () => { - window.location.hash = routing.getAutoFollowPatternPath(patterns[0].name); + routing.navigate(routing.getAutoFollowPatternPath(patterns[0].name)); }, } : null, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js index 6e4c019dab85c..101e3df6bf710 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js @@ -10,7 +10,7 @@ import { connect } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiLink, EuiOverlayMask } from '@elastic/eui'; - +import { reactRouterNavigate } from '../../../../../../src/plugins/kibana_react/public'; import { routing } from '../services/routing'; import { resumeFollowerIndex } from '../store/actions'; import { arrify } from '../../../common/services/utils'; @@ -97,7 +97,13 @@ class FollowerIndexResumeProviderUi extends PureComponent { custom advanced settings, {editLink}." values={{ editLink: ( - + ( @@ -152,10 +151,9 @@ export class RemoteClustersFormField extends PureComponent { {' '} {/* Break out of EuiFormRow's flexbox layout */} {this.errorMessages.noClusterFound()}

{description}

{this.errorMessages.remoteClusterDoesNotExist(name)}

{ +const renderApp = ( + element: Element, + I18nContext: I18nStart['Context'], + history: ScopedHistory, + getUrlForApp: ApplicationStart['getUrlForApp'] +): UnmountCallback => { render( - - - + , element @@ -36,17 +38,21 @@ export async function mountApp({ I18nContext, ELASTIC_WEBSITE_URL, DOC_LINK_VERSION, + history, + getUrlForApp, }: { element: Element; setBreadcrumbs: SetBreadcrumbs; I18nContext: I18nStart['Context']; ELASTIC_WEBSITE_URL: string; DOC_LINK_VERSION: string; + history: ScopedHistory; + getUrlForApp: ApplicationStart['getUrlForApp']; }): Promise { // Import and initialize additional services here instead of in plugin.ts to reduce the size of the // initial bundle as much as possible. initBreadcrumbs(setBreadcrumbs); initDocumentation(`${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`); - return renderApp(element, I18nContext); + return renderApp(element, I18nContext, history, getUrlForApp); } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js index 60a6cc79376e5..76fdf6e2fd766 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js @@ -27,7 +27,7 @@ export class AutoFollowPatternAdd extends PureComponent { }; componentDidMount() { - setBreadcrumbs([listBreadcrumb, addBreadcrumb]); + setBreadcrumbs([listBreadcrumb('/auto_follow_patterns'), addBreadcrumb]); } componentWillUnmount() { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js index 387d7817a0357..d89e3adb531ab 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPageContent, EuiSpacer } from '@elastic/eui'; import { listBreadcrumb, editBreadcrumb, setBreadcrumbs } from '../../services/breadcrumbs'; -import { routing } from '../../services/routing'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { AutoFollowPatternForm, AutoFollowPatternPageTitle, @@ -54,7 +54,7 @@ export class AutoFollowPatternEdit extends PureComponent { selectAutoFollowPattern(decodedId); - setBreadcrumbs([listBreadcrumb, editBreadcrumb]); + setBreadcrumbs([listBreadcrumb('/auto_follow_patterns'), editBreadcrumb]); } componentDidUpdate(prevProps, prevState) { @@ -108,7 +108,7 @@ export class AutoFollowPatternEdit extends PureComponent { @@ -122,7 +122,7 @@ export class AutoFollowPatternList extends PureComponent { {isAuthorized && ( (window.location.hash = routing.getAutoFollowPatternPath(name))} + onClick={() => routing.navigate(routing.getAutoFollowPatternPath(name))} data-test-subj="contextMenuEditButton" > diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js index 3f2ed82420ff1..6b2ee01bff8df 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js @@ -31,6 +31,7 @@ import { EuiTitle, } from '@elastic/eui'; +import { routing } from '../../../../../services/routing'; import { AutoFollowPatternIndicesPreview, AutoFollowPatternActionMenu, @@ -296,7 +297,12 @@ export class DetailPanel extends Component { - + { - const uri = routing.getFollowerIndexPath(id, '/edit', false); + const uri = routing.getFollowerIndexPath(id); routing.navigate(uri); }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js index 4436d76643e6c..a133c10b148aa 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js @@ -32,6 +32,7 @@ import { import 'brace/theme/textmate'; import { getIndexListUri } from '../../../../../../../../../plugins/index_management/public'; +import { routing } from '../../../../../services/routing'; import { API_STATUS } from '../../../../../constants'; import { ContextMenu } from '../context_menu'; @@ -452,7 +453,12 @@ export class DetailPanel extends Component { - + { - const uri = routing.getFollowerIndexPath(id, '/edit', false); + const uri = routing.getFollowerIndexPath(id); routing.navigate(uri); }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js index 7b843d08cefd3..4d4cbbf6825ec 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js @@ -17,7 +17,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { routing } from '../../../services/routing'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { extractQueryParams } from '../../../services/query_params'; import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { API_STATUS, UIM_FOLLOWER_INDEX_LIST_LOAD } from '../../../constants'; @@ -94,7 +94,7 @@ export class FollowerIndicesList extends PureComponent { } renderHeader() { - const { isAuthorized } = this.props; + const { isAuthorized, history } = this.props; return ( @@ -113,7 +113,7 @@ export class FollowerIndicesList extends PureComponent { {isAuthorized && ( { + setBreadcrumbs([listBreadcrumb(`/${section}`)]); routing.navigate(`/${section}`); }; @@ -94,12 +94,8 @@ export class CrossClusterReplicationHome extends PureComponent { - - + + diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts b/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts index 84ac9356462ad..c3ca893e6182b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts @@ -9,8 +9,6 @@ import { ChromeBreadcrumb } from 'src/core/public'; import { ManagementAppMountParams } from '../../../../../../src/plugins/management/public'; -import { BASE_PATH } from '../../../common/constants'; - export type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; let setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; @@ -19,11 +17,13 @@ export const init = (_setBreadcrumbs: SetBreadcrumbs): void => { setBreadcrumbs = _setBreadcrumbs; }; -export const listBreadcrumb = { - text: i18n.translate('xpack.crossClusterReplication.homeBreadcrumbTitle', { - defaultMessage: 'Cross-Cluster Replication', - }), - href: `#${BASE_PATH}`, +export const listBreadcrumb = (section?: string) => { + return { + text: i18n.translate('xpack.crossClusterReplication.homeBreadcrumbTitle', { + defaultMessage: 'Cross-Cluster Replication', + }), + href: section || '/', + }; }; export const addBreadcrumb = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/routing.js b/x-pack/plugins/cross_cluster_replication/public/app/services/routing.js index 1a488cc951c49..59210272534b9 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/routing.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/routing.js @@ -8,14 +8,8 @@ * This file based on guidance from https://github.com/elastic/eui/blob/master/wiki/react-router.md */ -import { createLocation } from 'history'; import { stringify } from 'query-string'; -import { APPS, BASE_PATH, BASE_PATH_REMOTE_CLUSTERS } from '../../../common/constants'; - -const isModifiedEvent = (event) => - !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); - -const isLeftClickEvent = (event) => event.button === 0; +import { BASE_PATH_REMOTE_CLUSTERS } from '../../../common/constants'; const queryParamsFromObject = (params, encodeParams = false) => { if (!params) { @@ -26,67 +20,31 @@ const queryParamsFromObject = (params, encodeParams = false) => { return `?${paramsStr}`; }; -const appToBasePathMap = { - [APPS.CCR_APP]: BASE_PATH, - [APPS.REMOTE_CLUSTER_APP]: BASE_PATH_REMOTE_CLUSTERS, -}; - class Routing { _reactRouter = null; - /** - * The logic for generating hrefs and onClick handlers from the `to` prop is largely borrowed from - * https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js. - * - * @param {*} to URL to navigate to - */ - getRouterLinkProps(to, base = BASE_PATH, params = {}, encodeParams = false) { + getHrefToRemoteClusters(route = '/', params, encodeParams = false) { const search = queryParamsFromObject(params, encodeParams) || ''; - const location = - typeof to === 'string' - ? createLocation(base + to + search, null, null, this._reactRouter.history.location) - : to; - const href = this._reactRouter.history.createHref(location); - - const onClick = (event) => { - if (event.defaultPrevented) { - return; - } - - // If target prop is set (e.g. to "_blank"), let browser handle link. - if (event.target.getAttribute('target')) { - return; - } - - if (isModifiedEvent(event) || !isLeftClickEvent(event)) { - return; - } - - // Prevent regular link behavior, which causes a browser refresh. - event.preventDefault(); - this._reactRouter.history.push(location); - }; - - return { href, onClick }; + return this._reactRouter.getUrlForApp('management', { + path: `${BASE_PATH_REMOTE_CLUSTERS}${route}${search}`, + }); } - navigate(route = '/home', app = APPS.CCR_APP, params, encodeParams = false) { + navigate(route = '/home', params, encodeParams = false) { const search = queryParamsFromObject(params, encodeParams); this._reactRouter.history.push({ - pathname: encodeURI(appToBasePathMap[app] + route), + pathname: encodeURI(route), search, }); } getAutoFollowPatternPath = (name, section = '/edit') => { - return encodeURI(`#${BASE_PATH}/auto_follow_patterns${section}/${encodeURIComponent(name)}`); + return encodeURI(`/auto_follow_patterns${section}/${encodeURIComponent(name)}`); }; - getFollowerIndexPath = (name, section = '/edit', withBase = true) => { - return withBase - ? encodeURI(`#${BASE_PATH}/follower_indices${section}/${encodeURIComponent(name)}`) - : encodeURI(`/follower_indices${section}/${encodeURIComponent(name)}`); + getFollowerIndexPath = (name, section = '/edit') => { + return encodeURI(`/follower_indices${section}/${encodeURIComponent(name)}`); }; get reactRouter() { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js index ea6801b55458d..6503333924951 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js @@ -76,7 +76,7 @@ export const saveAutoFollowPattern = (id, autoFollowPattern, isUpdating = false) ); getToasts().addSuccess(successMessage); - routing.navigate(`/auto_follow_patterns`, undefined, { + routing.navigate(`/auto_follow_patterns`, { pattern: encodeURIComponent(id), }); }, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js index 61d0ed1d51c72..1af5a95a29b98 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js @@ -77,7 +77,7 @@ export const saveFollowerIndex = (name, followerIndex, isUpdating = false) => ); getToasts().addSuccess(successMessage); - routing.navigate(`/follower_indices`, undefined, { + routing.navigate(`/follower_indices`, { name: encodeURIComponent(name), }); }, diff --git a/x-pack/plugins/cross_cluster_replication/public/plugin.ts b/x-pack/plugins/cross_cluster_replication/public/plugin.ts index e748822ab8ae7..8bf0d519e685d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/public/plugin.ts @@ -41,13 +41,14 @@ export class CrossClusterReplicationPlugin implements Plugin { id: MANAGEMENT_ID, title: PLUGIN.TITLE, order: 6, - mount: async ({ element, setBreadcrumbs }) => { + mount: async ({ element, setBreadcrumbs, history }) => { const { mountApp } = await import('./app'); const [coreStart] = await getStartServices(); const { i18n: { Context: I18nContext }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + application: { getUrlForApp }, } = coreStart; return mountApp({ @@ -56,12 +57,14 @@ export class CrossClusterReplicationPlugin implements Plugin { I18nContext, ELASTIC_WEBSITE_URL, DOC_LINK_VERSION, + history, + getUrlForApp, }); }, }); - ccrApp.disable(); - + // NOTE: We enable the plugin by default instead of disabling it by default because this + // creates a race condition that causes functional tests to fail on CI (see #66781). licensing.license$ .pipe(first()) .toPromise() @@ -76,8 +79,6 @@ export class CrossClusterReplicationPlugin implements Plugin { const isCcrUiEnabled = config.ui.enabled && remoteClusters.isUiEnabled; if (isLicenseOk && isCcrUiEnabled) { - ccrApp.enable(); - if (indexManagement) { const propertyPath = 'isFollowerIndex'; @@ -94,6 +95,8 @@ export class CrossClusterReplicationPlugin implements Plugin { indexManagement.extensionsService.addBadge(followerBadgeExtension); } + } else { + ccrApp.disable(); } }); } diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap index d64c8c6239fcd..9441ffd731524 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap @@ -92,6 +92,7 @@ Array [ exports[`extend index management ilm summary extension should return extension when index has lifecycle error 1`] = `
testy @@ -564,6 +565,7 @@ exports[`extend index management ilm summary extension should return extension w exports[`extend index management ilm summary extension should return extension when index has lifecycle policy 1`] = ` testy diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap index 857a63826505e..5edc5a9343fc3 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap @@ -91,11 +91,10 @@ exports[`policy table should show empty state when there are not any policies 1`
{ store = indexLifecycleManagementStore(); component = ( - + {}} /> ); store.dispatch(fetchedPolicies(policies)); @@ -90,7 +91,7 @@ describe('policy table', () => { store = indexLifecycleManagementStore(); component = ( - + {}} /> ); const rendered = mountWithIntl(component); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js index b5d9b91e8c412..4fa1838115840 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js +++ b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js @@ -115,6 +115,10 @@ const indexWithLifecycleError = { moment.tz.setDefault('utc'); +const getUrlForApp = (appId, options) => { + return appId + '/' + (options ? options.path : ''); +}; + describe('extend index management', () => { describe('retry lifecycle action extension', () => { test('should return null when no indices have index lifecycle policy', () => { @@ -171,13 +175,17 @@ describe('extend index management', () => { describe('add lifecycle policy action extension', () => { test('should return null when index has index lifecycle policy', () => { - const extension = addLifecyclePolicyActionExtension({ indices: [indexWithLifecyclePolicy] }); + const extension = addLifecyclePolicyActionExtension( + { indices: [indexWithLifecyclePolicy] }, + getUrlForApp + ); expect(extension).toBeNull(); }); test('should return null when more than one index is passed', () => { const extension = addLifecyclePolicyActionExtension({ indices: [indexWithoutLifecyclePolicy, indexWithoutLifecyclePolicy], + getUrlForApp, }); expect(extension).toBeNull(); }); @@ -185,6 +193,7 @@ describe('extend index management', () => { test('should return extension when one index is passed and it does not have lifecycle policy', () => { const extension = addLifecyclePolicyActionExtension({ indices: [indexWithoutLifecyclePolicy], + getUrlForApp, }); expect(extension.renderConfirmModal).toBeDefined; const component = extension.renderConfirmModal(jest.fn()); @@ -220,20 +229,20 @@ describe('extend index management', () => { describe('ilm summary extension', () => { test('should render null when index has no index lifecycle policy', () => { - const extension = ilmSummaryExtension(indexWithoutLifecyclePolicy); + const extension = ilmSummaryExtension(indexWithoutLifecyclePolicy, getUrlForApp); const rendered = mountWithIntl(extension); expect(rendered.isEmptyRender()).toBeTruthy(); }); test('should return extension when index has lifecycle policy', () => { - const extension = ilmSummaryExtension(indexWithLifecyclePolicy); + const extension = ilmSummaryExtension(indexWithLifecyclePolicy, getUrlForApp); expect(extension).toBeDefined(); const rendered = mountWithIntl(extension); expect(rendered).toMatchSnapshot(); }); test('should return extension when index has lifecycle error', () => { - const extension = ilmSummaryExtension(indexWithLifecycleError); + const extension = ilmSummaryExtension(indexWithLifecycleError, getUrlForApp); expect(extension).toBeDefined(); const rendered = mountWithIntl(extension); expect(rendered).toMatchSnapshot(); diff --git a/x-pack/plugins/index_lifecycle_management/common/constants/index.ts b/x-pack/plugins/index_lifecycle_management/common/constants/index.ts index 59e623934c60d..5c89b917163d8 100644 --- a/x-pack/plugins/index_lifecycle_management/common/constants/index.ts +++ b/x-pack/plugins/index_lifecycle_management/common/constants/index.ts @@ -17,6 +17,4 @@ export const PLUGIN = { }), }; -export const BASE_PATH = '/management/data/index_lifecycle_management/'; - export const API_BASE_PATH = '/api/index_lifecycle_management'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx index 993dced20bbe6..11cd5d181f4ad 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx @@ -5,25 +5,35 @@ */ import React, { useEffect } from 'react'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; +import { Router, Switch, Route, Redirect } from 'react-router-dom'; +import { ScopedHistory, ApplicationStart } from 'kibana/public'; import { METRIC_TYPE } from '@kbn/analytics'; -import { BASE_PATH } from '../../common/constants'; import { UIM_APP_LOAD } from './constants'; import { EditPolicy } from './sections/edit_policy'; import { PolicyTable } from './sections/policy_table'; import { trackUiMetric } from './services/ui_metric'; -export const App = () => { +export const App = ({ + history, + navigateToApp, +}: { + history: ScopedHistory; + navigateToApp: ApplicationStart['navigateToApp']; +}) => { useEffect(() => trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD), []); return ( - + - - - + + } + /> + - + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx index a7d88d31e58fc..eddbb5528ad84 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx @@ -7,17 +7,22 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Provider } from 'react-redux'; -import { I18nStart } from 'kibana/public'; +import { I18nStart, ScopedHistory, ApplicationStart } from 'kibana/public'; import { UnmountCallback } from 'src/core/public'; import { App } from './app'; import { indexLifecycleManagementStore } from './store'; -export const renderApp = (element: Element, I18nContext: I18nStart['Context']): UnmountCallback => { +export const renderApp = ( + element: Element, + I18nContext: I18nStart['Context'], + history: ScopedHistory, + navigateToApp: ApplicationStart['navigateToApp'] +): UnmountCallback => { render( - + , element diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js index 94186b7fc79d7..998143929afef 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js @@ -36,7 +36,6 @@ import { } from '../../constants'; import { toasts } from '../../services/notification'; -import { goToPolicyList } from '../../services/navigation'; import { findFirstError } from '../../services/find_errors'; import { LearnMoreLink } from '../components'; import { NodeAttrsDetails } from './components/node_attrs_details'; @@ -100,7 +99,7 @@ export class EditPolicy extends Component { backToPolicyList = () => { this.props.setSelectedPolicy(null); - goToPolicyList(); + this.props.history.push('/policies'); }; submit = async () => { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js index d9d74becf9e5d..dad259681eb7a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js @@ -36,9 +36,8 @@ import { } from '@elastic/eui'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; - +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; import { getIndexListUri } from '../../../../../../../index_management/public'; -import { BASE_PATH } from '../../../../../../common/constants'; import { UIM_EDIT_CLICK } from '../../../../constants'; import { getPolicyPath } from '../../../../services/navigation'; import { flattenPanelTree } from '../../../../services/flatten_panel_tree'; @@ -181,8 +180,9 @@ export class PolicyTable extends Component { /* eslint-disable-next-line @elastic/eui/href-or-on-click */ trackUiMetric('click', UIM_EDIT_CLICK)} + {...reactRouterNavigate(this.props.history, getPolicyPath(value), () => + trackUiMetric('click', UIM_EDIT_CLICK) + )} > {value} @@ -201,7 +201,7 @@ export class PolicyTable extends Component { renderCreatePolicyButton() { return ( { - window.location.hash = getIndexListUri(`ilm.policy:${policy.name}`); + this.props.navigateToApp('management', { + path: `/data/index_management${getIndexListUri(`ilm.policy:${policy.name}`)}`, + }); }, }); } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts index 2d518ebb3015e..72e9d51d8fdeb 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts @@ -4,12 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BASE_PATH } from '../../../common/constants'; - -export const goToPolicyList = () => { - window.location.hash = `${BASE_PATH}policies`; -}; - export const getPolicyPath = (policyName: string): string => { - return encodeURI(`#${BASE_PATH}policies/edit/${encodeURIComponent(policyName)}`); + return encodeURI(`/policies/edit/${encodeURIComponent(policyName)}`); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js index 110998a7e9354..0bd313c9a9f8d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js @@ -23,7 +23,6 @@ import { EuiModalHeaderTitle, } from '@elastic/eui'; -import { BASE_PATH } from '../../../common/constants'; import { loadPolicies, addLifecyclePolicyToIndex } from '../../application/services/api'; import { showApiError } from '../../application/services/api_errors'; import { toasts } from '../../application/services/notification'; @@ -216,7 +215,7 @@ export class AddLifecyclePolicyConfirmModal extends Component { } render() { const { policies } = this.state; - const { indexName, closeModal } = this.props; + const { indexName, closeModal, getUrlForApp } = this.props; const title = (

- + {value}; + content = ( + + {value} + + ); } else { content = value; } diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js index 43f8332f4b6bd..e7afc8f12859c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js @@ -67,7 +67,7 @@ export const removeLifecyclePolicyActionExtension = ({ indices, reloadIndices }) }; }; -export const addLifecyclePolicyActionExtension = ({ indices, reloadIndices }) => { +export const addLifecyclePolicyActionExtension = ({ indices, reloadIndices, getUrlForApp }) => { if (indices.length !== 1) { return null; } @@ -86,6 +86,7 @@ export const addLifecyclePolicyActionExtension = ({ indices, reloadIndices }) => closeModal={closeModal} index={index} reloadIndices={reloadIndices} + getUrlForApp={getUrlForApp} /> ); }, @@ -123,8 +124,8 @@ export const ilmBannerExtension = (indices) => { }; }; -export const ilmSummaryExtension = (index) => { - return ; +export const ilmSummaryExtension = (index, getUrlForApp) => { + return ; }; export const ilmFilterExtension = (indices) => { diff --git a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx index 8fce57b0e79b0..49856dee47fba 100644 --- a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx @@ -42,11 +42,12 @@ export class IndexLifecycleManagementPlugin { id: PLUGIN.ID, title: PLUGIN.TITLE, order: 2, - mount: async ({ element }) => { + mount: async ({ element, history }) => { const [coreStart] = await getStartServices(); const { i18n: { Context: I18nContext }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + application: { navigateToApp }, } = coreStart; // Initialize additional services. @@ -55,7 +56,7 @@ export class IndexLifecycleManagementPlugin { ); const { renderApp } = await import('./application'); - return renderApp(element, I18nContext); + return renderApp(element, I18nContext, history, navigateToApp); }, }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts index 57b925b8c6fc1..36d8e1d343b5e 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts @@ -14,7 +14,6 @@ import { nextTick, } from '../../../../../test_utils'; import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths -import { BASE_PATH } from '../../../common/constants'; import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { TemplateDeserialized } from '../../../common'; import { WithAppDependencies, services } from './setup_environment'; @@ -22,8 +21,8 @@ import { WithAppDependencies, services } from './setup_environment'; const testBedConfig: TestBedConfig = { store: () => indexManagementStore(services as any), memoryRouter: { - initialEntries: [`${BASE_PATH}indices?includeHidden=true`], - componentRoutePath: `${BASE_PATH}:section(indices|templates)`, + initialEntries: [`/indices?includeHidden=true`], + componentRoutePath: `/:section(indices|templates)`, }, doMountAsync: true, }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx index 05abe284fab32..8f464987418c0 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx @@ -59,7 +59,8 @@ const KEYWORD_MAPPING_FIELD = { type: 'keyword', }; -describe('', () => { +// FLAKY: https://github.com/elastic/kibana/issues/67833 +describe.skip('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/plugins/index_management/public/application/app.tsx b/x-pack/plugins/index_management/public/application/app.tsx index 83997dd6ece18..10bbe3ced64da 100644 --- a/x-pack/plugins/index_management/public/application/app.tsx +++ b/x-pack/plugins/index_management/public/application/app.tsx @@ -5,8 +5,9 @@ */ import React, { useEffect } from 'react'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; -import { BASE_PATH, UIM_APP_LOAD } from '../../common/constants'; +import { Router, Switch, Route, Redirect } from 'react-router-dom'; +import { ScopedHistory } from 'kibana/public'; +import { UIM_APP_LOAD } from '../../common/constants'; import { IndexManagementHome } from './sections/home'; import { TemplateCreate } from './sections/template_create'; import { TemplateClone } from './sections/template_clone'; @@ -14,24 +15,24 @@ import { TemplateEdit } from './sections/template_edit'; import { useServices } from './app_context'; -export const App = () => { +export const App = ({ history }: { history: ScopedHistory }) => { const { uiMetricService } = useServices(); useEffect(() => uiMetricService.trackMetric('loaded', UIM_APP_LOAD), [uiMetricService]); return ( - + - + ); }; // Export this so we can test it with a different router. export const AppWithoutRouter = () => ( - - - - - + + + + + ); diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 2bb618ad8efce..84938de416941 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -5,6 +5,7 @@ */ import React, { createContext, useContext } from 'react'; +import { ScopedHistory } from 'kibana/public'; import { CoreStart } from '../../../../../src/core/public'; import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public'; @@ -17,6 +18,7 @@ const AppContext = createContext(undefined); export interface AppDependencies { core: { fatalErrors: CoreStart['fatalErrors']; + getUrlForApp: CoreStart['application']['getUrlForApp']; }; plugins: { usageCollection: UsageCollectionSetup; @@ -27,6 +29,7 @@ export interface AppDependencies { httpService: HttpService; notificationService: NotificationService; }; + history: ScopedHistory; } export const AppContextProvider = ({ diff --git a/x-pack/plugins/index_management/public/application/index.tsx b/x-pack/plugins/index_management/public/application/index.tsx index 5850cb8d42f1a..8da556cc81fcc 100644 --- a/x-pack/plugins/index_management/public/application/index.tsx +++ b/x-pack/plugins/index_management/public/application/index.tsx @@ -24,13 +24,13 @@ export const renderApp = ( const { i18n } = core; const { Context: I18nContext } = i18n; - const { services } = dependencies; + const { services, history } = dependencies; render( - + , diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index c47b0603dc1c8..e8b6f200fb349 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -30,9 +30,9 @@ export async function mountManagementSection( services: InternalServices, params: ManagementAppMountParams ) { - const { element, setBreadcrumbs } = params; + const { element, setBreadcrumbs, history } = params; const [core] = await coreSetup.getStartServices(); - const { docLinks, fatalErrors } = core; + const { docLinks, fatalErrors, application } = core; breadcrumbService.setup(setBreadcrumbs); documentationService.setup(docLinks); @@ -40,11 +40,13 @@ export async function mountManagementSection( const appDependencies: AppDependencies = { core: { fatalErrors, + getUrlForApp: application.getUrlForApp, }, plugins: { usageCollection, }, services, + history, }; return renderApp(element, { core, dependencies: appDependencies }); diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx index 8e8616d24be20..9d4331d742a25 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx @@ -18,7 +18,6 @@ import { EuiTabs, EuiTitle, } from '@elastic/eui'; -import { BASE_PATH } from '../../../../common/constants'; import { documentationService } from '../../services/documentation'; import { IndexList } from './index_list'; import { TemplateList } from './template_list'; @@ -53,7 +52,7 @@ export const IndexManagementHome: React.FunctionComponent { - history.push(`${BASE_PATH}${newSection}`); + history.push(`/${newSection}`); }; useEffect(() => { @@ -107,9 +106,13 @@ export const IndexManagementHome: React.FunctionComponent - - - + + + diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js index e49b3c353931e..2fda71035fb58 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js @@ -54,14 +54,14 @@ const getHeaders = () => { }; export class Summary extends React.PureComponent { - getAdditionalContent(extensionsService) { + getAdditionalContent(extensionsService, getUrlForApp) { const { index } = this.props; const extensions = extensionsService.summaries; return extensions.map((summaryExtension, i) => { return ( - {summaryExtension(index)} + {summaryExtension(index, getUrlForApp)} ); }); @@ -103,9 +103,12 @@ export class Summary extends React.PureComponent { render() { return ( - {({ services }) => { + {({ services, core }) => { const { left, right } = this.buildRows(); - const additionalContent = this.getAdditionalContent(services.extensionsService); + const additionalContent = this.getAdditionalContent( + services.extensionsService, + core.getUrlForApp + ); return ( diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js index effd80c39f0d1..1931884cf7306 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js @@ -46,7 +46,7 @@ export class IndexActionsContextMenu extends Component { confirmAction = (isActionConfirmed) => { this.setState({ isActionConfirmed }); }; - panels({ services: { extensionsService } }) { + panels({ services: { extensionsService }, core: { getUrlForApp } }) { const { closeIndices, openIndices, @@ -214,6 +214,7 @@ export class IndexActionsContextMenu extends Component { const actionExtensionDefinition = actionExtension({ indices, reloadIndices, + getUrlForApp, }); if (actionExtensionDefinition) { const { diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx index bc942b6b3f55b..db0833ea03233 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx @@ -7,6 +7,7 @@ import React, { Fragment, useState, useEffect, useMemo } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ScopedHistory } from 'kibana/public'; import { EuiEmptyPrompt, EuiSpacer, @@ -144,6 +145,7 @@ export const TemplateList: React.FunctionComponent ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx index 36ae5104ea092..1c487158c2022 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx @@ -8,18 +8,20 @@ import React, { useState, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiInMemoryTable, EuiIcon, EuiButton, EuiLink, EuiBasicTableColumn } from '@elastic/eui'; +import { ScopedHistory } from 'kibana/public'; import { TemplateListItem, IndexTemplateFormatVersion } from '../../../../../../common'; -import { BASE_PATH, UIM_TEMPLATE_SHOW_DETAILS_CLICK } from '../../../../../../common/constants'; +import { UIM_TEMPLATE_SHOW_DETAILS_CLICK } from '../../../../../../common/constants'; import { TemplateDeleteModal } from '../../../../components'; import { useServices } from '../../../../app_context'; -import { getTemplateDetailsLink } from '../../../../services/routing'; import { SendRequestResponse } from '../../../../../shared_imports'; +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; interface Props { templates: TemplateListItem[]; reload: () => Promise; editTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void; cloneTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void; + history: ScopedHistory; } export const TemplateTable: React.FunctionComponent = ({ @@ -27,6 +29,7 @@ export const TemplateTable: React.FunctionComponent = ({ reload, editTemplate, cloneTemplate, + history, }) => { const { uiMetricService } = useServices(); const [selection, setSelection] = useState([]); @@ -46,9 +49,15 @@ export const TemplateTable: React.FunctionComponent = ({ return ( /* eslint-disable-next-line @elastic/eui/href-or-on-click */ uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK) + )} data-test-subj="templateDetailsLink" - onClick={() => uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK)} > {name} @@ -237,11 +246,11 @@ export const TemplateTable: React.FunctionComponent = ({ /> , { if (filter) { // React router tries to decode url params but it can't because the browser partially // decodes them. So we have to encode both the URL and the filter to get it all to // work correctly for filters with URL unsafe characters in them. - return encodeURI(`#${BASE_PATH}indices/filter/${encodeURIComponent(filter)}`); + return encodeURI(`/indices/filter/${encodeURIComponent(filter)}`); } // If no filter, URI is already safe so no need to encode. - return `#${BASE_PATH}indices`; + return '/indices'; }; export const getILMPolicyPath = (policyName: string) => { - return encodeURI( - `#/management/data/index_lifecycle_management/policies/edit/${encodeURIComponent(policyName)}` - ); + return encodeURI(`/policies/edit/${encodeURIComponent(policyName)}`); }; diff --git a/x-pack/plugins/index_management/public/application/services/routing.ts b/x-pack/plugins/index_management/public/application/services/routing.ts index a6d8f67751cd1..fe118b1181082 100644 --- a/x-pack/plugins/index_management/public/application/services/routing.ts +++ b/x-pack/plugins/index_management/public/application/services/routing.ts @@ -3,11 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { BASE_PATH } from '../../../common/constants'; import { IndexTemplateFormatVersion } from '../../../common'; export const getTemplateListLink = () => { - return `${BASE_PATH}templates`; + return `/templates`; }; // Need to add some additonal encoding/decoding logic to work with React Router @@ -17,22 +16,20 @@ export const getTemplateDetailsLink = ( formatVersion: IndexTemplateFormatVersion, withHash = false ) => { - const baseUrl = `${BASE_PATH}templates/${encodeURIComponent( - encodeURIComponent(name) - )}?v=${formatVersion}`; + const baseUrl = `/templates/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}`; const url = withHash ? `#${baseUrl}` : baseUrl; return encodeURI(url); }; export const getTemplateEditLink = (name: string, formatVersion: IndexTemplateFormatVersion) => { return encodeURI( - `${BASE_PATH}edit_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}` + `/edit_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}` ); }; export const getTemplateCloneLink = (name: string, formatVersion: IndexTemplateFormatVersion) => { return encodeURI( - `${BASE_PATH}clone_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}` + `/clone_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}` ); }; diff --git a/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx index fcb29e3eb1c02..9ddf422871d18 100644 --- a/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx +++ b/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx @@ -8,6 +8,7 @@ import React, { useMemo } from 'react'; import { EuiFlexItem } from '@elastic/eui'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { WaffleSortControls } from '../../../../public/pages/metrics/inventory_view/components/waffle/waffle_sort_controls'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ToolbarProps } from '../../../../public/pages/metrics/inventory_view/components/toolbars/toolbar'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { WaffleMetricControls } from '../../../../public/pages/metrics/inventory_view/components/waffle/metric_control'; diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json index ea66ae7a46d4e..4701182c96813 100644 --- a/x-pack/plugins/infra/kibana.json +++ b/x-pack/plugins/infra/kibana.json @@ -10,7 +10,7 @@ "data", "dataEnhanced", "visTypeTimeseries", - "alerting", + "alerts", "triggers_actions_ui" ], "server": true, 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 bafb38459b17b..52033a00327c0 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 @@ -35,7 +35,7 @@ export const MetricsAlertDropdown = () => { icon="tableOfContents" key="manageLink" href={kibana.services?.application?.getUrlForApp( - 'kibana#/management/insightsAndAlerting/triggersActions/alerts' + 'management/insightsAndAlerting/triggersActions/alerts' )} > 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 a3cebcf33f386..c48b5b9a2cc58 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 @@ -35,7 +35,7 @@ export const InventoryAlertDropdown = () => { icon="tableOfContents" key="manageLink" href={kibana.services?.application?.getUrlForApp( - 'kibana#/management/insightsAndAlerting/triggersActions/alerts' + 'management/insightsAndAlerting/triggersActions/alerts' )} > diff --git a/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx index d808b4f3b64aa..b8eb73b99f45e 100644 --- a/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx @@ -15,8 +15,8 @@ export const AlertDropdown = () => { const [flyoutVisible, setFlyoutVisible] = useState(false); const manageAlertsLinkProps = useLinkProps( { - app: 'kibana', - hash: 'management/insightsAndAlerting/triggersActions/alerts', + app: 'management', + pathname: '/insightsAndAlerting/triggersActions/alerts', }, { hrefOnly: true, 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 a6d66d47975c0..5fe9a45a7ceed 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 @@ -194,7 +194,10 @@ const useFetchEntriesEffect = ( } }; - const runFetchMoreEntriesRequest = async (direction: ShouldFetchMoreEntries) => { + const runFetchMoreEntriesRequest = async ( + direction: ShouldFetchMoreEntries, + overrides: Partial = {} + ) => { if (!props.startTimestamp || !props.endTimestamp) { return; } @@ -209,10 +212,10 @@ const useFetchEntriesEffect = ( try { const commonFetchArgs: LogEntriesBaseRequest = { - sourceId: props.sourceId, - startTimestamp: props.startTimestamp, - endTimestamp: props.endTimestamp, - query: props.filterQuery, + sourceId: overrides.sourceId || props.sourceId, + startTimestamp: overrides.startTimestamp || props.startTimestamp, + endTimestamp: overrides.endTimestamp || props.endTimestamp, + query: overrides.filterQuery || props.filterQuery, }; const fetchArgs: LogEntriesRequest = getEntriesBefore @@ -279,10 +282,10 @@ const useFetchEntriesEffect = ( const streamEntriesEffect = () => { (async () => { if (props.isStreaming && !state.isLoadingMore && !state.isReloading) { + const endTimestamp = Date.now(); if (startedStreaming) { await new Promise((res) => setTimeout(res, LIVE_STREAM_INTERVAL)); } else { - const endTimestamp = Date.now(); props.jumpToTargetPosition({ tiebreaker: 0, time: endTimestamp }); setStartedStreaming(true); if (state.hasMoreAfterEnd) { @@ -290,7 +293,9 @@ const useFetchEntriesEffect = ( return; } } - const newEntriesEnd = await runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After); + const newEntriesEnd = await runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After, { + endTimestamp, + }); if (newEntriesEnd) { props.jumpToTargetPosition(newEntriesEnd); } diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 4bbbf8dcdee03..d00afbc7b497a 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -13,7 +13,7 @@ import { SpacesPluginSetup } from '../../../../../../plugins/spaces/server'; import { VisTypeTimeseriesSetup } from '../../../../../../../src/plugins/vis_type_timeseries/server'; import { APMPluginSetup } from '../../../../../../plugins/apm/server'; import { HomeServerPluginSetup } from '../../../../../../../src/plugins/home/server'; -import { PluginSetupContract as AlertingPluginContract } from '../../../../../../plugins/alerting/server'; +import { PluginSetupContract as AlertingPluginContract } from '../../../../../alerts/server'; // NP_TODO: Compose real types from plugins we depend on, no "any" export interface InfraServerPluginDeps { @@ -23,7 +23,7 @@ export interface InfraServerPluginDeps { visTypeTimeseries: VisTypeTimeseriesSetup; features: FeaturesPluginSetup; apm: APMPluginSetup; - alerting: AlertingPluginContract; + alerts: AlertingPluginContract; } export interface CallWithRequestParams extends GenericParams { diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index b36de2a3bd091..5a34a6665e781 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -11,7 +11,7 @@ import { CallWithRequestParams, } from '../../adapters/framework/adapter_types'; import { Comparator, AlertStates, InventoryMetricConditions } from './types'; -import { AlertServices, AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertServices, AlertExecutorOptions } from '../../../../../alerts/server'; import { InfraSnapshot } from '../../snapshot'; import { parseFilterQuery } from '../../../utils/serialized_query'; import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts index 995d415ef3c8f..a3b9e85458416 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts @@ -11,12 +11,12 @@ import { LogDocumentCountAlertParams, Criterion, } from '../../../../common/alerting/logs/types'; -import { AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertExecutorOptions } from '../../../../../alerts/server'; import { alertsMock, AlertInstanceMock, AlertServicesMock, -} from '../../../../../alerting/server/mocks'; +} from '../../../../../alerts/server/mocks'; import { libsMock } from './mocks'; interface AlertTestInstance { diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index eedaf4202b37d..ee4e1fcb3f6e2 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { AlertExecutorOptions, AlertServices } from '../../../../../alerting/server'; +import { AlertExecutorOptions, AlertServices } from '../../../../../alerts/server'; import { AlertStates, Comparator, diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts index cdb4d2d968479..ed7e82fe29e4c 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts @@ -6,7 +6,7 @@ import uuid from 'uuid'; import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; -import { PluginSetupContract } from '../../../../../alerting/server'; +import { PluginSetupContract } from '../../../../../alerts/server'; import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor'; import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index 19efc88e216ca..8260ebed84622 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -6,12 +6,12 @@ import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor'; import { Comparator, AlertStates } from './types'; import * as mocks from './test_mocks'; -import { AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertExecutorOptions } from '../../../../../alerts/server'; import { alertsMock, AlertServicesMock, AlertInstanceMock, -} from '../../../../../alerting/server/mocks'; +} from '../../../../../alerts/server/mocks'; import { InfraSources } from '../../sources'; interface AlertTestInstance { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index d1cb60112aa42..233a34a67d1ec 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -17,7 +17,7 @@ import { DOCUMENT_COUNT_I18N, stateToAlertMessage, } from './messages'; -import { AlertServices, AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertServices, AlertExecutorOptions } from '../../../../../alerts/server'; import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; import { getDateHistogramOffset } from '../../snapshot/query_helpers'; import { InfraBackendLibs } from '../../infra_types'; diff --git a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts index ae74ed82038fd..989a2917b0520 100644 --- a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginSetupContract } from '../../../../alerting/server'; +import { PluginSetupContract } from '../../../../alerts/server'; import { registerMetricThresholdAlertType } from './metric_threshold/register_metric_threshold_alert_type'; import { registerMetricInventoryThresholdAlertType } from './inventory_metric_threshold/register_inventory_metric_threshold_alert_type'; import { registerLogThresholdAlertType } from './log_threshold/register_log_threshold_alert_type'; diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index a265d53fc1bf8..2fd614830c05d 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -149,7 +149,7 @@ export class InfraServerPlugin { ]); initInfraServer(this.libs); - registerAlertTypes(plugins.alerting, this.libs); + registerAlertTypes(plugins.alerts, this.libs); // Telemetry UsageCollector.registerUsageCollector(plugins.usageCollection); diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts index abb266da9f066..3309d8497f4c5 100644 --- a/x-pack/plugins/ingest_manager/common/constants/routes.ts +++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts @@ -46,6 +46,7 @@ export const AGENT_CONFIG_API_ROUTES = { UPDATE_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}`, DELETE_PATTERN: `${AGENT_CONFIG_API_ROOT}/delete`, FULL_INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/full`, + FULL_INFO_DOWNLOAD_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/download`, }; // Output API routes diff --git a/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts b/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts new file mode 100644 index 0000000000000..9dfd76b9ddd21 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { safeDump } from 'js-yaml'; +import { FullAgentConfig } from '../types'; + +const CONFIG_KEYS_ORDER = [ + 'id', + 'name', + 'revision', + 'type', + 'outputs', + 'settings', + 'datasources', + 'enabled', + 'package', + 'input', +]; + +export const configToYaml = (config: FullAgentConfig): string => { + return safeDump(config, { + skipInvalid: true, + sortKeys: (keyA: string, keyB: string) => { + const indexA = CONFIG_KEYS_ORDER.indexOf(keyA); + const indexB = CONFIG_KEYS_ORDER.indexOf(keyB); + if (indexA >= 0 && indexB < 0) { + return -1; + } + + if (indexA < 0 && indexB >= 0) { + return 1; + } + + return indexA - indexB; + }, + }); +}; diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/ingest_manager/common/services/index.ts index 91dbbdd515c3e..c595c9a52f66f 100644 --- a/x-pack/plugins/ingest_manager/common/services/index.ts +++ b/x-pack/plugins/ingest_manager/common/services/index.ts @@ -8,5 +8,6 @@ import * as AgentStatusKueryHelper from './agent_status'; export * from './routes'; export { packageToConfigDatasourceInputs, packageToConfigDatasource } from './package_to_config'; export { storedDatasourceToAgentDatasource } from './datasource_to_agent_datasource'; +export { configToYaml } from './config_to_yaml'; export { AgentStatusKueryHelper }; export { decodeCloudId } from './decode_cloud_id'; diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts index 20d040ac6eaee..3fc990ea9d70c 100644 --- a/x-pack/plugins/ingest_manager/common/services/routes.ts +++ b/x-pack/plugins/ingest_manager/common/services/routes.ts @@ -90,6 +90,13 @@ export const agentConfigRouteService = { getInfoFullPath: (agentConfigId: string) => { return AGENT_CONFIG_API_ROUTES.FULL_INFO_PATTERN.replace('{agentConfigId}', agentConfigId); }, + + getInfoFullDownloadPath: (agentConfigId: string) => { + return AGENT_CONFIG_API_ROUTES.FULL_INFO_DOWNLOAD_PATTERN.replace( + '{agentConfigId}', + agentConfigId + ); + }, }; export const dataStreamRouteService = { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx new file mode 100644 index 0000000000000..8a9f0553895a1 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonIcon, + EuiContextMenu, + EuiContextMenuPanel, + EuiPopover, + EuiButton, +} from '@elastic/eui'; +import { EuiButtonProps } from '@elastic/eui/src/components/button/button'; +import { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu'; +import { EuiContextMenuPanelProps } from '@elastic/eui/src/components/context_menu/context_menu_panel'; + +type Props = { + button?: { + props: EuiButtonProps; + children: JSX.Element; + }; +} & ( + | { + items: EuiContextMenuPanelProps['items']; + } + | { + panels: EuiContextMenuProps['panels']; + } +); + +export const ContextMenuActions = React.memo(({ button, ...props }) => { + const [isOpen, setIsOpen] = useState(false); + const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); + const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); + + return ( + + {button.children} + + ) : ( + + ) + } + isOpen={isOpen} + closePopover={handleCloseMenu} + > + {'items' in props ? ( + + ) : ( + + )} + + ); +}); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts index b0b4e79cece79..93bc0645c7eee 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts @@ -7,4 +7,7 @@ export { Loading } from './loading'; export { Error } from './error'; export { Header, HeaderProps } from './header'; export { AlphaMessaging } from './alpha_messaging'; +export { PackageIcon } from './package_icon'; +export { ContextMenuActions } from './context_menu_actions'; +export { SearchBar } from './search_bar'; export * from './settings_flyout'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx deleted file mode 100644 index 56f010e2fa774..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useCallback, useState } from 'react'; -import { EuiButtonIcon, EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu'; - -export const TableRowActionsNested = React.memo<{ panels: EuiContextMenuProps['panels'] }>( - ({ panels }) => { - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); - - return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - ); - } -); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts index 73771fa3cb343..5ef7f45faec48 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts @@ -43,7 +43,6 @@ export const PAGE_ROUTING_PATHS = { configurations: '/configs', configurations_list: '/configs', configuration_details: '/configs/:configId/:tabId?', - configuration_details_yaml: '/configs/:configId/yaml', configuration_details_settings: '/configs/:configId/settings', add_datasource_from_configuration: '/configs/:configId/add-datasource', add_datasource_from_integration: '/integrations/:pkgkey/add-datasource', diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts index a752ad2a8912b..31a511f2d79dc 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts @@ -10,6 +10,7 @@ export { useConfig, ConfigContext } from './use_config'; export { useSetupDeps, useStartDeps, DepsContext } from './use_deps'; export { useBreadcrumbs } from './use_breadcrumbs'; export { useLink } from './use_link'; +export { useKibanaLink } from './use_kibana_link'; export { usePackageIconType, UsePackageIconType } from './use_package_icon_type'; export { usePagination, Pagination } from './use_pagination'; export { useDebounce } from './use_debounce'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts index f80c468677f48..45ca6047b0d96 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts @@ -14,6 +14,7 @@ import { agentConfigRouteService } from '../../services'; import { GetAgentConfigsResponse, GetOneAgentConfigResponse, + GetFullAgentConfigResponse, CreateAgentConfigRequest, CreateAgentConfigResponse, UpdateAgentConfigRequest, @@ -39,7 +40,7 @@ export const useGetOneAgentConfig = (agentConfigId: string | undefined) => { }; export const useGetOneAgentConfigFull = (agentConfigId: string) => { - return useRequest({ + return useRequest({ path: agentConfigRouteService.getInfoFullPath(agentConfigId), method: 'get', }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx new file mode 100644 index 0000000000000..78ed228012691 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx @@ -0,0 +1,69 @@ +/* + * 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, { memo, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; +import { useCapabilities, useLink } from '../../../hooks'; +import { ContextMenuActions } from '../../../components'; +import { ConfigYamlFlyout } from './config_yaml_flyout'; + +export const AgentConfigActionMenu = memo<{ configId: string; fullButton?: boolean }>( + ({ configId, fullButton = false }) => { + const { getHref } = useLink(); + const hasWriteCapabilities = useCapabilities().write; + const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState(false); + return ( + <> + {isYamlFlyoutOpen ? ( + + setIsYamlFlyoutOpen(false)} /> + + ) : null} + + ), + } + : undefined + } + items={[ + setIsYamlFlyoutOpen(!isYamlFlyoutOpen)} + key="viewConfig" + > + + , + + + , + ]} + /> + + ); + } +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_form.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_form.tsx index 30996931ba67a..73ddd567c515b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_form.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_form.tsx @@ -50,6 +50,15 @@ export const agentConfigFormValidation = ( ]; } + if (!agentConfig.namespace?.trim()) { + errors.namespace = [ + , + ]; + } + return errors; }; @@ -73,7 +82,6 @@ export const AgentConfigForm: React.FunctionComponent = ({ onDelete = () => {}, }) => { const [touchedFields, setTouchedFields] = useState<{ [key: string]: boolean }>({}); - const [showNamespace, setShowNamespace] = useState(!!agentConfig.namespace); const fields: Array<{ name: 'name' | 'description' | 'namespace'; label: JSX.Element; @@ -170,49 +178,28 @@ export const AgentConfigForm: React.FunctionComponent = ({ /> } > - - } - checked={showNamespace} - onChange={() => { - setShowNamespace(!showNamespace); - if (showNamespace) { - updateAgentConfig({ namespace: '' }); - } - }} - /> - {showNamespace && ( - <> - - - { - updateAgentConfig({ namespace: value }); - }} - onChange={(selectedOptions) => { - updateAgentConfig({ - namespace: (selectedOptions.length ? selectedOptions[0] : '') as string, - }); - }} - isInvalid={Boolean(touchedFields.namespace && validation.namespace)} - onBlur={() => setTouchedFields({ ...touchedFields, namespace: true })} - /> - - - )} + + { + updateAgentConfig({ namespace: value }); + }} + onChange={(selectedOptions) => { + updateAgentConfig({ + namespace: (selectedOptions.length ? selectedOptions[0] : '') as string, + }); + }} + isInvalid={Boolean(touchedFields.namespace && validation.namespace)} + onBlur={() => setTouchedFields({ ...touchedFields, namespace: true })} + /> + void }>( + ({ configId, onClose }) => { + const core = useCore(); + const { isLoading: isLoadingYaml, data: yamlData } = useGetOneAgentConfigFull(configId); + const { data: configData } = useGetOneAgentConfig(configId); + + const body = + isLoadingYaml && !yamlData ? ( + + ) : ( + + {configToYaml(yamlData!.item)} + + ); + + const downloadLink = core.http.basePath.prepend( + agentConfigRouteService.getInfoFullDownloadPath(configId) + ); + + return ( + + + +

+ {configData?.item ? ( + + ) : ( + + )} +

+ + + {body} + + + + + + + + + + + + + + + + ); + } +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts index c1811b99588a8..f3ec15e0f477d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts @@ -3,8 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - export { AgentConfigForm, agentConfigFormValidation } from './config_form'; export { AgentConfigDeleteProvider } from './config_delete_provider'; +export { DatasourceDeleteProvider } from './datasource_delete_provider'; export { LinkedAgentCount } from './linked_agent_count'; export { ConfirmDeployConfigModal } from './confirm_deploy_modal'; +export { DangerEuiContextMenuItem } from './danger_eui_context_menu_item'; +export { AgentConfigActionMenu } from './actions_menu'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx deleted file mode 100644 index 2f9a11ef76704..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useCallback, useState } from 'react'; -import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { EuiContextMenuPanelProps } from '@elastic/eui/src/components/context_menu/context_menu_panel'; - -export const TableRowActions = React.memo<{ items: EuiContextMenuPanelProps['items'] }>( - ({ items }) => { - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); - - return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - ); - } -); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts index 992ace3530f40..67cde2dec3a56 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts @@ -106,6 +106,18 @@ describe('Ingest Manager - validateDatasource()', () => { { dataset: 'disabled2', input: 'disabled2', title: 'Disabled 2', enabled: false }, ], }, + { + type: 'with-no-stream-vars', + enabled: true, + vars: [{ required: true, name: 'var-name', type: 'text' }], + streams: [ + { + id: 'with-no-stream-vars-bar', + dataset: 'bar', + enabled: true, + }, + ], + }, ], }, ], @@ -172,12 +184,26 @@ describe('Ingest Manager - validateDatasource()', () => { vars: { 'var-name': { value: undefined, type: 'text' } }, }, { - id: 'with-disabled-streams-disabled2', + id: 'with-disabled-streams-disabled-without-vars', dataset: 'disabled2', enabled: false, }, ], }, + { + type: 'with-no-stream-vars', + enabled: true, + vars: { + 'var-name': { value: 'test', type: 'text' }, + }, + streams: [ + { + id: 'with-no-stream-vars-bar', + dataset: 'bar', + enabled: true, + }, + ], + }, ], }; @@ -245,12 +271,26 @@ describe('Ingest Manager - validateDatasource()', () => { }, }, { - id: 'with-disabled-streams-disabled2', + id: 'with-disabled-streams-disabled-without-vars', dataset: 'disabled2', enabled: false, }, ], }, + { + type: 'with-no-stream-vars', + enabled: true, + vars: { + 'var-name': { value: undefined, type: 'text' }, + }, + streams: [ + { + id: 'with-no-stream-vars-bar', + dataset: 'bar', + enabled: true, + }, + ], + }, ], }; @@ -274,7 +314,18 @@ describe('Ingest Manager - validateDatasource()', () => { }, }, 'with-disabled-streams': { - streams: { 'with-disabled-streams-disabled': { vars: { 'var-name': null } } }, + streams: { + 'with-disabled-streams-disabled': { + vars: { 'var-name': null }, + }, + 'with-disabled-streams-disabled-without-vars': {}, + }, + }, + 'with-no-stream-vars': { + streams: { + 'with-no-stream-vars-bar': {}, + }, + vars: { 'var-name': null }, }, }, }; @@ -307,7 +358,16 @@ describe('Ingest Manager - validateDatasource()', () => { }, }, 'with-disabled-streams': { - streams: { 'with-disabled-streams-disabled': { vars: { 'var-name': null } } }, + streams: { + 'with-disabled-streams-disabled': { vars: { 'var-name': null } }, + 'with-disabled-streams-disabled-without-vars': {}, + }, + }, + 'with-no-stream-vars': { + vars: { + 'var-name': ['var-name is required'], + }, + streams: { 'with-no-stream-vars-bar': {} }, }, }, }); @@ -354,7 +414,18 @@ describe('Ingest Manager - validateDatasource()', () => { }, }, 'with-disabled-streams': { - streams: { 'with-disabled-streams-disabled': { vars: { 'var-name': null } } }, + streams: { + 'with-disabled-streams-disabled': { + vars: { 'var-name': null }, + }, + 'with-disabled-streams-disabled-without-vars': {}, + }, + }, + 'with-no-stream-vars': { + vars: { + 'var-name': ['var-name is required'], + }, + streams: { 'with-no-stream-vars-bar': {} }, }, }, }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts index 61273e1fb3db9..5b4cfe170a478 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts @@ -110,36 +110,31 @@ export const validateDatasource = ( // Validate each input stream with config fields if (input.streams.length) { input.streams.forEach((stream) => { - if (!stream.vars) { - return; - } - - const streamValidationResults: DatasourceConfigValidationResults = { - vars: undefined, - }; - - const streamVarsByName = ( - ( - registryInputsByType[input.type].streams.find( - (registryStream) => registryStream.dataset === stream.dataset - ) || {} - ).vars || [] - ).reduce((vars, registryVar) => { - vars[registryVar.name] = registryVar; - return vars; - }, {} as Record); + const streamValidationResults: DatasourceConfigValidationResults = {}; // Validate stream-level config fields - streamValidationResults.vars = Object.entries(stream.vars).reduce( - (results, [name, configEntry]) => { - results[name] = - input.enabled && stream.enabled - ? validateDatasourceConfig(configEntry, streamVarsByName[name]) - : null; - return results; - }, - {} as ValidationEntry - ); + if (stream.vars) { + const streamVarsByName = ( + ( + registryInputsByType[input.type].streams.find( + (registryStream) => registryStream.dataset === stream.dataset + ) || {} + ).vars || [] + ).reduce((vars, registryVar) => { + vars[registryVar.name] = registryVar; + return vars; + }, {} as Record); + streamValidationResults.vars = Object.entries(stream.vars).reduce( + (results, [name, configEntry]) => { + results[name] = + input.enabled && stream.enabled + ? validateDatasourceConfig(configEntry, streamVarsByName[name]) + : null; + return results; + }, + {} as ValidationEntry + ); + } inputValidationResults.streams![stream.id] = streamValidationResults; }); @@ -228,5 +223,6 @@ export const validationHasErrors = ( | DatasourceConfigValidationResults ) => { const flattenedValidation = getFlattenedObject(validationResults); + return !!Object.entries(flattenedValidation).find(([, value]) => !!value); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx index 316b7eed491b9..01505fcf4c65e 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx @@ -17,12 +17,10 @@ import { EuiFlexItem, } from '@elastic/eui'; import { AgentConfig, Datasource } from '../../../../../types'; -import { TableRowActions } from '../../../components/table_row_actions'; -import { DangerEuiContextMenuItem } from '../../../components/danger_eui_context_menu_item'; +import { PackageIcon, ContextMenuActions } from '../../../../../components'; +import { DatasourceDeleteProvider, DangerEuiContextMenuItem } from '../../../components'; import { useCapabilities, useLink } from '../../../../../hooks'; -import { DatasourceDeleteProvider } from '../../../components/datasource_delete_provider'; -import { useConfigRefresh } from '../../hooks/use_config'; -import { PackageIcon } from '../../../../../components/package_icon'; +import { useConfigRefresh } from '../../hooks'; interface InMemoryDatasource extends Datasource { streams: { total: number; enabled: number }; @@ -197,7 +195,7 @@ export const DatasourcesTable: React.FunctionComponent = ({ actions: [ { render: (datasource: InMemoryDatasource) => ( - (({ config }) => { - const fullConfigRequest = useGetOneAgentConfigFull(config.id); - - if (fullConfigRequest.isLoading && !fullConfigRequest.data) { - return ; - } - - return ( - - - - {dump(fullConfigRequest.data.item, { - sortKeys: (keyA: string, keyB: string) => { - const indexA = CONFIG_KEYS_ORDER.indexOf(keyA); - const indexB = CONFIG_KEYS_ORDER.indexOf(keyB); - if (indexA >= 0 && indexB < 0) { - return -1; - } - - if (indexA < 0 && indexB >= 0) { - return 1; - } - - return indexA - indexB; - }, - })} - - - - ); -}); 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 3f886645b5339..1dd7e660deaa9 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 @@ -27,10 +27,8 @@ import { useGetOneAgentConfig, useLink, useBreadcrumbs } from '../../../hooks'; import { Loading } from '../../../components'; import { WithHeaderLayout } from '../../../layouts'; import { ConfigRefreshContext, useGetAgentStatus, AgentStatusRefreshContext } from './hooks'; -import { LinkedAgentCount } from '../components'; -import { ConfigDatasourcesView } from './components/datasources'; -import { ConfigYamlView } from './components/yaml'; -import { ConfigSettingsView } from './components/settings'; +import { LinkedAgentCount, AgentConfigActionMenu } from '../components'; +import { ConfigDatasourcesView, ConfigSettingsView } from './components'; const Divider = styled.div` width: 0; @@ -147,21 +145,31 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { )) || '', }, + { isDivider: true }, + { + content: agentConfig && , + }, ].map((item, index) => ( {item.isDivider ?? false ? ( - ) : ( + ) : item.label ? ( - {item.label} - {item.content} + + {item.label} + + + {item.content} + + ) : ( + item.content )} ))} ), - [agentConfig, agentStatus] + [agentConfig, configId, agentStatus] ); const headerTabs = useMemo(() => { @@ -174,14 +182,6 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { href: getHref('configuration_details', { configId, tabId: 'datasources' }), isSelected: tabId === '' || tabId === 'datasources', }, - { - id: 'yaml', - name: i18n.translate('xpack.ingestManager.configDetails.subTabs.yamlTabText', { - defaultMessage: 'YAML', - }), - href: getHref('configuration_details', { configId, tabId: 'yaml' }), - isSelected: tabId === 'yaml', - }, { id: 'settings', name: i18n.translate('xpack.ingestManager.configDetails.subTabs.settingsTabText', { @@ -254,12 +254,6 @@ const AgentConfigDetailsContent: React.FunctionComponent<{ agentConfig: AgentCon useBreadcrumbs('configuration_details', { configName: agentConfig.name }); return ( - { - return ; - }} - /> { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx index 5b4066e53f0c8..0d43d8856c2fb 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.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, { CSSProperties, memo, useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiSpacer, EuiText, @@ -16,7 +16,6 @@ import { EuiTableActionsColumnType, EuiTableFieldDataColumnType, EuiTextColor, - EuiContextMenuItem, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; @@ -33,16 +32,9 @@ import { useUrlParams, useBreadcrumbs, } from '../../../hooks'; +import { SearchBar } from '../../../components'; +import { LinkedAgentCount, AgentConfigActionMenu } from '../components'; import { CreateAgentConfigFlyout } from './components'; -import { SearchBar } from '../../../components/search_bar'; -import { LinkedAgentCount } from '../components'; -import { TableRowActions } from '../components/table_row_actions'; - -const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', -}); const AgentConfigListPageLayout: React.FunctionComponent = ({ children }) => ( ( ); -const ConfigRowActions = memo<{ config: AgentConfig; onDelete: () => void }>( - ({ config, onDelete }) => { - const { getHref } = useLink(); - const hasWriteCapabilities = useCapabilities().write; - - return ( - - - , - - - - , - // - // - // , - ]} - /> - ); - } -); - export const AgentConfigListPage: React.FunctionComponent<{}> = () => { useBreadcrumbs('configurations_list'); const { getHref, getPath } = useLink(); @@ -172,10 +122,10 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { width: '20%', render: (name: string, agentConfig: AgentConfig) => ( - + {name || agentConfig.id} @@ -201,7 +151,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { width: '35%', truncateText: true, render: (description: AgentConfig['description']) => ( - + {description} ), @@ -239,9 +189,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { }), actions: [ { - render: (config: AgentConfig) => ( - sendRequest()} /> - ), + render: (config: AgentConfig) => , }, ], }, @@ -253,7 +201,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { } return cols; - }, [getHref, isFleetEnabled, sendRequest]); + }, [getHref, isFleetEnabled]); const createAgentConfigButton = useMemo( () => ( 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 b87ae4c4561ff..cdc4f1c63a11d 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 @@ -5,12 +5,11 @@ */ import React, { memo } from 'react'; - -import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { useKibanaLink } from '../../../../hooks/use_kibana_link'; +import { FormattedMessage } from '@kbn/i18n/react'; import { DataStream } from '../../../../types'; -import { TableRowActionsNested } from '../../../../components/table_row_actions_nested'; +import { useKibanaLink } from '../../../../hooks'; +import { ContextMenuActions } from '../../../../components'; export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastream }) => { const { dashboards } = datastream; @@ -78,5 +77,5 @@ export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastre }); } - return ; + return ; }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx index 34a7ad8eb1efc..27e17f6b3df61 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx @@ -3,81 +3,70 @@ * 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, { memo, useState, useCallback } from 'react'; -import { EuiButton, EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; +import React, { memo, useState } from 'react'; +import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; import { useCapabilities } from '../../../../hooks'; -import { useAgentRefresh } from '../hooks'; +import { ContextMenuActions } from '../../../../components'; import { AgentUnenrollProvider, AgentReassignConfigFlyout } from '../../components'; +import { useAgentRefresh } from '../hooks'; export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; }> = memo(({ agent }) => { const hasWriteCapabilites = useCapabilities().write; const refreshAgent = useAgentRefresh(); - const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsActionsPopoverOpen(false), [ - setIsActionsPopoverOpen, - ]); - const handleToggleMenu = useCallback(() => setIsActionsPopoverOpen(!isActionsPopoverOpen), [ - isActionsPopoverOpen, - ]); const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); return ( <> {isReassignFlyoutOpen && ( - setIsReassignFlyoutOpen(false)} /> + + setIsReassignFlyoutOpen(false)} /> + )} - + -
- } - isOpen={isActionsPopoverOpen} - closePopover={handleCloseMenu} - > - { - handleCloseMenu(); - setIsReassignFlyoutOpen(true); - }} - key="reassignConfig" - > - - , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, refreshAgent); - }} - > - - - )} - , - ]} - /> - + ), + }} + items={[ + { + setIsReassignFlyoutOpen(true); + }} + key="reassignConfig" + > + + , + + {(unenrollAgentsPrompt) => ( + { + unenrollAgentsPrompt([agent.id], 1, refreshAgent); + }} + > + + + )} + , + ]} + /> ); }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx index d5b8b393e7ed9..281a8d3a9745c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.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, { useState, useCallback } from 'react'; +import React, { useState } from 'react'; import { EuiBasicTable, EuiButton, @@ -17,10 +17,9 @@ import { EuiPopover, EuiSpacer, EuiText, - EuiButtonIcon, - EuiContextMenuPanel, EuiContextMenuItem, EuiIcon, + EuiPortal, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; @@ -36,12 +35,10 @@ import { useLink, useBreadcrumbs, } from '../../../hooks'; -import { AgentReassignConfigFlyout } from '../components'; -import { SearchBar } from '../../../components/search_bar'; -import { AgentHealth } from '../components/agent_health'; -import { AgentUnenrollProvider } from '../components/agent_unenroll_provider'; +import { SearchBar, ContextMenuActions } from '../../../components'; import { AgentStatusKueryHelper } from '../../../services'; import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; +import { AgentReassignConfigFlyout, AgentHealth, AgentUnenrollProvider } from '../components'; const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ overflow: 'hidden', @@ -76,73 +73,53 @@ const RowActions = React.memo<{ agent: Agent; onReassignClick: () => void; refre ({ agent, refresh, onReassignClick }) => { const { getHref } = useLink(); const hasWriteCapabilites = useCapabilities().write; - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - , - { - handleCloseMenu(); - onReassignClick(); - }} - key="reassignConfig" - > - - , + + + , + { + onReassignClick(); + }} + key="reassignConfig" + > + + , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, () => { - refresh(); - }); - }} - > - - - )} - , - ]} - /> - + + {(unenrollAgentsPrompt) => ( + { + unenrollAgentsPrompt([agent.id], 1, () => { + refresh(); + }); + }} + > + + + )} + , + ]} + /> ); } ); @@ -387,13 +364,15 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { /> ) : null} {agentToReassign && ( - { - setAgentToReassignId(undefined); - agentsRequest.sendRequest(); - }} - /> + + { + setAgentToReassignId(undefined); + agentsRequest.sendRequest(); + }} + /> + )} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts index 085bad2d18375..6dbc8d67caaee 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -20,5 +20,6 @@ export { appRoutesService, packageToConfigDatasourceInputs, storedDatasourceToAgentDatasource, + configToYaml, AgentStatusKueryHelper, } from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 0a26a16d35cfd..05a97fd2e2a3c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -26,6 +26,7 @@ export { GetAgentConfigsResponse, GetAgentConfigsResponseItem, GetOneAgentConfigResponse, + GetFullAgentConfigResponse, CreateAgentConfigRequest, CreateAgentConfigResponse, UpdateAgentConfigRequest, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts index f74f898b2baf9..afc146cf90447 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { TypeOf } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; +import { RequestHandler, ResponseHeaders } from 'src/core/server'; import bluebird from 'bluebird'; +import { configToYaml } from '../../../common/services'; import { appContextService, agentConfigService, datasourceService } from '../../services'; import { listAgents } from '../../services/agents'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; @@ -229,3 +230,37 @@ export const getFullAgentConfig: RequestHandler> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const { + params: { agentConfigId }, + } = request; + + try { + const fullAgentConfig = await agentConfigService.getFullConfig(soClient, agentConfigId); + if (fullAgentConfig) { + const body = configToYaml(fullAgentConfig); + const headers: ResponseHeaders = { + 'content-type': 'text/x-yaml', + 'content-disposition': `attachment; filename="elastic-agent-config-${fullAgentConfig.id}.yml"`, + }; + return response.ok({ + body, + headers, + }); + } else { + return response.customError({ + statusCode: 404, + body: { message: 'Agent config not found' }, + }); + } + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts index e630f3c959590..4f6cfb436b93b 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts @@ -20,6 +20,7 @@ import { updateAgentConfigHandler, deleteAgentConfigsHandler, getFullAgentConfig, + downloadFullAgentConfig, } from './handlers'; export const registerRoutes = (router: IRouter) => { @@ -82,4 +83,14 @@ export const registerRoutes = (router: IRouter) => { }, getFullAgentConfig ); + + // Download one full agent config + router.get( + { + path: AGENT_CONFIG_API_ROUTES.FULL_INFO_DOWNLOAD_PATTERN, + validate: GetFullAgentConfigRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-read`] }, + }, + downloadFullAgentConfig + ); }; diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts index 20b62eee9a317..5bbc376051122 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts @@ -30,6 +30,7 @@ export async function agentCheckin( const updateData: { last_checkin: string; default_api_key?: string; + default_api_key_id?: string; local_metadata?: AgentMetadata; current_error_events?: string; } = { @@ -51,11 +52,13 @@ export async function agentCheckin( // Assign output API keys // We currently only support default ouput if (!defaultApiKey) { - updateData.default_api_key = await APIKeysService.generateOutputApiKey( + const outputAPIKey = await APIKeysService.generateOutputApiKey( soClient, 'default', agent.id ); + updateData.default_api_key = outputAPIKey.key; + updateData.default_api_key_id = outputAPIKey.id; } // Mutate the config to set the api token for this agent config.outputs.default.api_key = defaultApiKey || updateData.default_api_key; diff --git a/x-pack/plugins/ingest_manager/server/services/api_keys/index.ts b/x-pack/plugins/ingest_manager/server/services/api_keys/index.ts index 7d8d372a89ac4..6c95dc831aa9a 100644 --- a/x-pack/plugins/ingest_manager/server/services/api_keys/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/api_keys/index.ts @@ -17,7 +17,7 @@ export async function generateOutputApiKey( soClient: SavedObjectsClientContract, outputId: string, agentId: string -): Promise { +): Promise<{ key: string; id: string }> { const name = `${agentId}:${outputId}`; const key = await createAPIKey(soClient, name, { 'fleet-output': { @@ -35,7 +35,7 @@ export async function generateOutputApiKey( throw new Error('Unable to create an output api key'); } - return `${key.id}:${key.api_key}`; + return { key: `${key.id}:${key.api_key}`, id: key.id }; } export async function generateAccessApiKey( diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index 7c0d5d571f6a5..736711f9152e9 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -52,22 +52,22 @@ export async function ensureInstalledDefaultPackages( const installations = []; for (const pkgName in DefaultPackages) { if (!DefaultPackages.hasOwnProperty(pkgName)) continue; - const installation = await ensureInstalledPackage({ + const installation = ensureInstalledPackage({ savedObjectsClient, pkgName, callCluster, }); - if (installation) installations.push(installation); + installations.push(installation); } - return installations; + return Promise.all(installations); } export async function ensureInstalledPackage(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; callCluster: CallESAsCurrentUser; -}): Promise { +}): Promise { const { savedObjectsClient, pkgName, callCluster } = options; const installedPackage = await getInstallation({ savedObjectsClient, pkgName }); if (installedPackage) { @@ -79,7 +79,9 @@ export async function ensureInstalledPackage(options: { pkgName, callCluster, }); - return await getInstallation({ savedObjectsClient, pkgName }); + const installation = await getInstallation({ savedObjectsClient, pkgName }); + if (!installation) throw new Error(`could not get installation ${pkgName}`); + return installation; } export async function installPackage(options: { diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/ingest_manager/server/services/index.ts index 483661b9de915..1a0fb262eeb7f 100644 --- a/x-pack/plugins/ingest_manager/server/services/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/index.ts @@ -5,7 +5,7 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AgentStatus } from '../../common/types/models'; +import { AgentStatus } from '../types'; import * as settingsService from './settings'; export { ESIndexPatternSavedObjectService } from './es_index_pattern'; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts index ab97ddc0ba723..123a413bb8442 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts @@ -39,4 +39,7 @@ export const GetFullAgentConfigRequestSchema = { params: schema.object({ agentConfigId: schema.string(), }), + query: schema.object({ + download: schema.maybe(schema.boolean()), + }), }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx index 3243d665832f2..fa8c4f82c1b68 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx @@ -5,13 +5,15 @@ */ /* eslint-disable @kbn/eslint/no-restricted-paths */ import React from 'react'; - +import { LocationDescriptorObject } from 'history'; +import { ScopedHistory } from 'kibana/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { notificationServiceMock, fatalErrorsServiceMock, docLinksServiceMock, injectedMetadataServiceMock, + scopedHistoryMock, } from '../../../../../../src/core/public/mocks'; import { usageCollectionPluginMock } from '../../../../../../src/plugins/usage_collection/public/mocks'; @@ -33,12 +35,18 @@ const httpServiceSetupMock = new HttpService().setup({ fatalErrors: fatalErrorsServiceMock.createSetupContract(), }); +const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; +history.createHref = (location: LocationDescriptorObject) => { + return `${location.pathname}?${location.search}`; +}; + const appServices = { breadcrumbs: breadcrumbService, metric: uiMetricService, documentation: documentationService, api: apiService, notifications: notificationServiceMock.createSetupContract(), + history, }; export const setupEnvironment = () => { diff --git a/x-pack/plugins/ingest_pipelines/common/constants.ts b/x-pack/plugins/ingest_pipelines/common/constants.ts index de291e364e02f..4c6c6fefaad83 100644 --- a/x-pack/plugins/ingest_pipelines/common/constants.ts +++ b/x-pack/plugins/ingest_pipelines/common/constants.ts @@ -11,7 +11,7 @@ export const PLUGIN_ID = 'ingest_pipelines'; export const PLUGIN_MIN_LICENSE_TYPE = basicLicense; -export const BASE_PATH = '/management/ingest/ingest_pipelines'; +export const BASE_PATH = '/'; export const API_BASE_PATH = '/api/ingest_pipelines'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/app.tsx b/x-pack/plugins/ingest_pipelines/public/application/app.tsx index f4ac640722120..55b59caab8d60 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/app.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/app.tsx @@ -6,9 +6,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageContent } from '@elastic/eui'; import React, { FunctionComponent } from 'react'; -import { HashRouter, Switch, Route } from 'react-router-dom'; +import { Router, Switch, Route } from 'react-router-dom'; -import { BASE_PATH, APP_CLUSTER_REQUIRED_PRIVILEGES } from '../../common/constants'; +import { useKibana } from '../shared_imports'; + +import { APP_CLUSTER_REQUIRED_PRIVILEGES } from '../../common/constants'; import { SectionError, @@ -22,10 +24,10 @@ import { PipelinesList, PipelinesCreate, PipelinesEdit, PipelinesClone } from '. export const AppWithoutRouter = () => ( - - - - + + + + {/* Catch all */} @@ -33,6 +35,7 @@ export const AppWithoutRouter = () => ( export const App: FunctionComponent = () => { const { apiError } = useAuthorizationContext(); + const { history } = useKibana().services; if (apiError) { return ( @@ -91,9 +94,9 @@ export const App: FunctionComponent = () => { } return ( - + - + ); }} diff --git a/x-pack/plugins/ingest_pipelines/public/application/index.tsx b/x-pack/plugins/ingest_pipelines/public/application/index.tsx index e43dba4689b44..a8e6febeb2e59 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/index.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/index.tsx @@ -8,6 +8,7 @@ import { HttpSetup } from 'kibana/public'; import React, { ReactNode } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { NotificationsSetup } from 'kibana/public'; +import { ManagementAppMountParams } from 'src/plugins/management/public'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { API_BASE_PATH } from '../../common/constants'; @@ -23,6 +24,7 @@ export interface AppServices { documentation: DocumentationService; api: ApiService; notifications: NotificationsSetup; + history: ManagementAppMountParams['history']; } export interface CoreServices { diff --git a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts index e36f27cbf5f62..49c8f5a7b2e1e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts @@ -13,7 +13,7 @@ export async function mountManagementSection( { http, getStartServices, notifications }: CoreSetup, params: ManagementAppMountParams ) { - const { element, setBreadcrumbs } = params; + const { element, setBreadcrumbs, history } = params; const [coreStart] = await getStartServices(); const { docLinks, @@ -29,6 +29,7 @@ export async function mountManagementSection( documentation: documentationService, api: apiService, notifications, + history, }; return renderApp(element, I18nContext, services, { http }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx index f6fe2f0cf65fa..eba69ff454911 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx @@ -6,12 +6,15 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiEmptyPrompt, EuiLink, EuiPageBody, EuiPageContent } from '@elastic/eui'; -import { BASE_PATH } from '../../../../common/constants'; +import { EuiEmptyPrompt, EuiLink, EuiPageBody, EuiPageContent, EuiButton } from '@elastic/eui'; +import { useHistory } from 'react-router-dom'; +import { ScopedHistory } from 'kibana/public'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { useKibana } from '../../../shared_imports'; export const EmptyList: FunctionComponent = () => { const { services } = useKibana(); + const history = useHistory() as ScopedHistory; return ( @@ -41,7 +44,7 @@ export const EmptyList: FunctionComponent = () => {

} actions={ - + {i18n.translate('xpack.ingestPipelines.list.table.emptyPrompt.createButtonLabel', { defaultMessage: 'Create a pipeline', })} diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx index 541a2b486b5a7..97775965f9b45 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx @@ -13,9 +13,10 @@ import { EuiInMemoryTableProps, EuiTableFieldDataColumnType, } from '@elastic/eui'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; -import { BASE_PATH } from '../../../../common/constants'; import { Pipeline } from '../../../../common/types'; +import { useKibana } from '../../../shared_imports'; export interface Props { pipelines: Pipeline[]; @@ -32,6 +33,7 @@ export const PipelineTable: FunctionComponent = ({ onClonePipelineClick, onDeletePipelineClick, }) => { + const { history } = useKibana().services; const [selection, setSelection] = useState([]); const tableProps: EuiInMemoryTableProps = { @@ -80,14 +82,14 @@ export const PipelineTable: FunctionComponent = ({ })} , {i18n.translate('xpack.ingestPipelines.list.table.createPipelineButtonLabel', { - defaultMessage: 'Create a pipeline', + defaultMessage: 'Create a pipeline here', })} , ], @@ -107,7 +109,10 @@ export const PipelineTable: FunctionComponent = ({ }), sortable: true, render: (name: string) => ( - + {name} ), diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts index 1ccdbbad9b1bb..5fc8e13e3dcad 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { BASE_PATH } from '../../../common/constants'; import { ManagementAppMountParams } from '../../../../../../src/plugins/management/public'; type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; @@ -28,7 +27,7 @@ export class BreadcrumbService { create: [ { text: homeBreadcrumbText, - href: `#${BASE_PATH}`, + href: `/`, }, { text: i18n.translate('xpack.ingestPipelines.breadcrumb.createPipelineLabel', { @@ -39,7 +38,7 @@ export class BreadcrumbService { edit: [ { text: homeBreadcrumbText, - href: `#${BASE_PATH}`, + href: `/`, }, { text: i18n.translate('xpack.ingestPipelines.breadcrumb.editPipelineLabel', { diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 1762965478292..f1a2edd2d554f 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -9,6 +9,7 @@ import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { App } from './app'; import { EditorFrameInstance } from '../types'; +import { AppMountParameters } from 'kibana/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { Document, SavedObjectStore } from '../persistence'; import { mount } from 'enzyme'; @@ -111,6 +112,7 @@ describe('Lens App', () => { newlyCreated?: boolean ) => void; originatingApp: string | undefined; + onAppLeave: AppMountParameters['onAppLeave']; }> { return ({ navigation: navigationStartMock, @@ -153,6 +155,7 @@ describe('Lens App', () => { newlyCreated?: boolean ) => {} ), + onAppLeave: jest.fn(), } as unknown) as jest.Mocked<{ navigation: typeof navigationStartMock; editorFrame: EditorFrameInstance; @@ -168,6 +171,7 @@ describe('Lens App', () => { newlyCreated?: boolean ) => void; originatingApp: string | undefined; + onAppLeave: AppMountParameters['onAppLeave']; }>; } @@ -357,22 +361,7 @@ describe('Lens App', () => { newTitle: string; } - let defaultArgs: jest.Mocked<{ - editorFrame: EditorFrameInstance; - navigation: typeof navigationStartMock; - data: typeof dataStartMock; - core: typeof core; - storage: Storage; - docId?: string; - docStorage: SavedObjectStore; - redirectTo: ( - id?: string, - returnToOrigin?: boolean, - originatingApp?: string | undefined, - newlyCreated?: boolean - ) => void; - originatingApp: string | undefined; - }>; + let defaultArgs: ReturnType; beforeEach(() => { defaultArgs = makeDefaultArgs(); @@ -486,30 +475,6 @@ describe('Lens App', () => { expect(getButton(instance).disableButton).toEqual(true); }); - it('shows a disabled save button when there are no changes to the document', async () => { - const args = defaultArgs; - (args.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'My cool doc', - expression: '', - } as jest.ResolvedValue); - args.editorFrame = frame; - - instance = mount(); - expect(getButton(instance).disableButton).toEqual(true); - - const onChange = frame.mount.mock.calls[0][1].onChange; - - act(() => { - onChange({ - filterableIndexPatterns: [], - doc: ({ id: '1234', expression: 'valid expression' } as unknown) as Document, - }); - }); - instance.update(); - expect(getButton(instance).disableButton).toEqual(false); - }); - it('shows a save button that is enabled when the frame has provided its state', async () => { const args = defaultArgs; args.editorFrame = frame; @@ -691,21 +656,7 @@ describe('Lens App', () => { }); describe('query bar state management', () => { - let defaultArgs: jest.Mocked<{ - editorFrame: EditorFrameInstance; - data: typeof dataStartMock; - navigation: typeof navigationStartMock; - core: typeof core; - storage: Storage; - docId?: string; - docStorage: SavedObjectStore; - redirectTo: ( - id?: string, - returnToOrigin?: boolean, - originatingApp?: string | undefined, - newlyCreated?: boolean - ) => void; - }>; + let defaultArgs: ReturnType; beforeEach(() => { defaultArgs = makeDefaultArgs(); @@ -1001,4 +952,159 @@ describe('Lens App', () => { expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled(); }); + + describe('showing a confirm message when leaving', () => { + let defaultArgs: ReturnType; + let defaultLeave: jest.Mock; + let confirmLeave: jest.Mock; + + beforeEach(() => { + defaultArgs = makeDefaultArgs(); + defaultLeave = jest.fn(); + confirmLeave = jest.fn(); + (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ + id: '1234', + title: 'My cool doc', + expression: 'valid expression', + state: { + query: 'kuery', + datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] }, + }, + } as jest.ResolvedValue); + }); + + it('should not show a confirm message if there is no expression to save', () => { + instance = mount(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(defaultLeave).toHaveBeenCalled(); + expect(confirmLeave).not.toHaveBeenCalled(); + }); + + it('does not confirm if the user is missing save permissions', () => { + const args = defaultArgs; + args.core.application = { + ...args.core.application, + capabilities: { + ...args.core.application.capabilities, + visualize: { save: false, saveQuery: false, show: true }, + }, + }; + args.editorFrame = frame; + + instance = mount(); + + const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => + onChange({ + filterableIndexPatterns: [], + doc: ({ id: undefined, expression: 'valid expression' } as unknown) as Document, + }) + ); + instance.update(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(defaultLeave).toHaveBeenCalled(); + expect(confirmLeave).not.toHaveBeenCalled(); + }); + + it('should confirm when leaving with an unsaved doc', () => { + defaultArgs.editorFrame = frame; + instance = mount(); + + const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => + onChange({ + filterableIndexPatterns: [], + doc: ({ id: undefined, expression: 'valid expression' } as unknown) as Document, + }) + ); + instance.update(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(confirmLeave).toHaveBeenCalled(); + expect(defaultLeave).not.toHaveBeenCalled(); + }); + + it('should confirm when leaving with unsaved changes to an existing doc', async () => { + defaultArgs.editorFrame = frame; + instance = mount(); + await act(async () => { + instance.setProps({ docId: '1234' }); + }); + + const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => + onChange({ + filterableIndexPatterns: [], + doc: ({ id: '1234', expression: 'different expression' } as unknown) as Document, + }) + ); + instance.update(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(confirmLeave).toHaveBeenCalled(); + expect(defaultLeave).not.toHaveBeenCalled(); + }); + + it('should not confirm when changes are saved', async () => { + defaultArgs.editorFrame = frame; + instance = mount(); + await act(async () => { + instance.setProps({ docId: '1234' }); + }); + + const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => + onChange({ + filterableIndexPatterns: [], + doc: ({ id: '1234', expression: 'valid expression' } as unknown) as Document, + }) + ); + instance.update(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(defaultLeave).toHaveBeenCalled(); + expect(confirmLeave).not.toHaveBeenCalled(); + }); + + it('should confirm when the latest doc is invalid', async () => { + defaultArgs.editorFrame = frame; + instance = mount(); + await act(async () => { + instance.setProps({ docId: '1234' }); + }); + + const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => + onChange({ + filterableIndexPatterns: [], + doc: ({ id: '1234', expression: null } as unknown) as Document, + }) + ); + instance.update(); + + const lastCall = + defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + lastCall({ default: defaultLeave, confirm: confirmLeave }); + + expect(confirmLeave).toHaveBeenCalled(); + expect(defaultLeave).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index a77fbbb597564..ffa59a6fb6bc9 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -10,7 +10,7 @@ import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { Query, DataPublicPluginStart } from 'src/plugins/data/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; -import { AppMountContext, NotificationsStart } from 'kibana/public'; +import { AppMountContext, AppMountParameters, NotificationsStart } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { @@ -57,6 +57,7 @@ export function App({ redirectTo, originatingAppFromUrl, navigation, + onAppLeave, }: { editorFrame: EditorFrameInstance; data: DataPublicPluginStart; @@ -72,6 +73,7 @@ export function App({ newlyCreated?: boolean ) => void; originatingAppFromUrl?: string | undefined; + onAppLeave: AppMountParameters['onAppLeave']; }) { const language = storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage'); @@ -94,6 +96,12 @@ export function App({ const { lastKnownDoc } = state; + const isSaveable = + lastKnownDoc && + lastKnownDoc.expression && + lastKnownDoc.expression.length > 0 && + core.application.capabilities.visualize.save; + useEffect(() => { // Clear app-specific filters when navigating to Lens. Necessary because Lens // can be loaded without a full page refresh @@ -123,7 +131,31 @@ export function App({ filterSubscription.unsubscribe(); timeSubscription.unsubscribe(); }; - }, []); + }, [data.query.filterManager, data.query.timefilter.timefilter]); + + useEffect(() => { + onAppLeave((actions) => { + // Confirm when the user has made any changes to an existing doc + // or when the user has configured something without saving + if ( + core.application.capabilities.visualize.save && + (state.persistedDoc?.expression + ? !_.isEqual(lastKnownDoc?.expression, state.persistedDoc.expression) + : lastKnownDoc?.expression) + ) { + return actions.confirm( + i18n.translate('xpack.lens.app.unsavedWorkMessage', { + defaultMessage: 'Leave Lens with unsaved work?', + }), + i18n.translate('xpack.lens.app.unsavedWorkTitle', { + defaultMessage: 'Unsaved changes', + }) + ); + } else { + return actions.default(); + } + }); + }, [lastKnownDoc, onAppLeave, state.persistedDoc, core.application.capabilities.visualize.save]); // Sync Kibana breadcrumbs any time the saved document's title changes useEffect(() => { @@ -144,7 +176,7 @@ export function App({ : i18n.translate('xpack.lens.breadcrumbsCreate', { defaultMessage: 'Create' }), }, ]); - }, [state.persistedDoc && state.persistedDoc.title]); + }, [core.application, core.chrome, core.http.basePath, state.persistedDoc]); useEffect(() => { if (docId && (!state.persistedDoc || state.persistedDoc.id !== docId)) { @@ -187,13 +219,16 @@ export function App({ redirectTo(); }); } - }, [docId]); - - const isSaveable = - lastKnownDoc && - lastKnownDoc.expression && - lastKnownDoc.expression.length > 0 && - core.application.capabilities.visualize.save; + }, [ + core.notifications, + data.indexPatterns, + data.query.filterManager, + docId, + // TODO: These dependencies are changing too often + // docStorage, + // redirectTo, + // state.persistedDoc, + ]); const runSave = ( saveProps: Omit & { @@ -257,7 +292,7 @@ export function App({ core.notifications.toasts.addDanger({ title: e.message, }), - [] + [core.notifications.toasts] ); const { TopNavMenu } = navigation.ui; diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 7c875935f6320..032ce8325dca1 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -92,6 +92,7 @@ export async function mountApp( redirectTo(routeProps, id, returnToOrigin, originatingApp, newlyCreated) } originatingAppFromUrl={originatingAppFromUrl} + onAppLeave={params.onAppLeave} /> ); }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss index 3fbc42f9a25a0..924f44a37c459 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss @@ -31,3 +31,6 @@ min-height: $euiSizeXXL; } +.lnsLayerPanel__styleEditor { + width: $euiSize * 28; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index 0d86a051b0faa..e53e465c18950 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -45,7 +45,6 @@ function LayerPanels( } ) { const { - framePublicAPI, activeVisualization, visualizationState, dispatch, @@ -109,12 +108,10 @@ function LayerPanels( {...props} key={layerId} layerId={layerId} - activeVisualization={activeVisualization} visualizationState={visualizationState} updateVisualization={setVisualizationState} updateDatasource={updateDatasource} updateAll={updateAll} - frame={framePublicAPI} isOnlyLayer={layerIds.length === 1} onRemoveLayer={() => { dispatch({ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx index f89b6ef32d3f7..cc8d97a445016 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx @@ -36,7 +36,7 @@ export function DimensionPopover({ (popoverState.openId === accessor || (noMatch && popoverState.addingToGroupId === groupId)) } closePopover={() => { - setPopoverState({ isOpen: false, openId: null, addingToGroupId: null }); + setPopoverState({ isOpen: false, openId: null, addingToGroupId: null, tabId: null }); }} button={trigger} anchorPosition="leftUp" diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx new file mode 100644 index 0000000000000..1f987f86d3950 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -0,0 +1,271 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { + createMockVisualization, + createMockFramePublicAPI, + createMockDatasource, + DatasourceMock, +} from '../../mocks'; +import { EuiFormRow, EuiPopover } from '@elastic/eui'; +import { mount } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { Visualization } from '../../../types'; +import { LayerPanel } from './layer_panel'; +import { coreMock } from 'src/core/public/mocks'; +import { generateId } from '../../../id_generator'; + +jest.mock('../../../id_generator'); + +describe('LayerPanel', () => { + let mockVisualization: jest.Mocked; + let mockDatasource: DatasourceMock; + + function getDefaultProps() { + const frame = createMockFramePublicAPI(); + frame.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; + return { + layerId: 'first', + activeVisualizationId: 'vis1', + visualizationMap: { + vis1: mockVisualization, + }, + activeDatasourceId: 'ds1', + datasourceMap: { + ds1: mockDatasource, + }, + datasourceStates: { + ds1: { + isLoading: false, + state: 'state', + }, + }, + visualizationState: 'state', + updateVisualization: jest.fn(), + updateDatasource: jest.fn(), + updateAll: jest.fn(), + framePublicAPI: frame, + isOnlyLayer: true, + onRemoveLayer: jest.fn(), + dispatch: jest.fn(), + core: coreMock.createStart(), + }; + } + + beforeEach(() => { + mockVisualization = { + ...createMockVisualization(), + id: 'testVis', + visualizationTypes: [ + { + icon: 'empty', + id: 'testVis', + label: 'TEST1', + }, + ], + }; + + mockVisualization.getLayerIds.mockReturnValue(['first']); + mockDatasource = createMockDatasource('ds1'); + }); + + it('should fail to render if the public API is out of date', () => { + const props = getDefaultProps(); + props.framePublicAPI.datasourceLayers = {}; + const component = mountWithIntl(); + expect(component.isEmptyRender()).toBe(true); + }); + + it('should fail to render if the active visualization is missing', () => { + const component = mountWithIntl( + + ); + expect(component.isEmptyRender()).toBe(true); + }); + + describe('layer reset and remove', () => { + it('should show the reset button when single layer', () => { + const component = mountWithIntl(); + expect(component.find('[data-test-subj="lns_layer_remove"]').first().text()).toContain( + 'Reset layer' + ); + }); + + it('should show the delete button when multiple layers', () => { + const component = mountWithIntl(); + expect(component.find('[data-test-subj="lns_layer_remove"]').first().text()).toContain( + 'Delete layer' + ); + }); + + it('should call the clear callback', () => { + const cb = jest.fn(); + const component = mountWithIntl(); + act(() => { + component.find('[data-test-subj="lns_layer_remove"]').first().simulate('click'); + }); + expect(cb).toHaveBeenCalled(); + }); + }); + + describe('single group', () => { + it('should render the non-editable state', () => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: ['x'], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + ], + }); + + const component = mountWithIntl(); + + const group = component.find('DragDrop[data-test-subj="lnsGroup"]'); + expect(group).toHaveLength(1); + }); + + it('should render the group with a way to add a new column', () => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: [], + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + }, + ], + }); + + const component = mountWithIntl(); + + const group = component.find('DragDrop[data-test-subj="lnsGroup"]'); + expect(group).toHaveLength(1); + }); + + it('should render the required warning when only one group is configured', () => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: ['x'], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + { + groupLabel: 'B', + groupId: 'b', + accessors: [], + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + required: true, + }, + ], + }); + + const component = mountWithIntl(); + + const group = component + .find(EuiFormRow) + .findWhere((e) => e.prop('error') === 'Required dimension'); + expect(group).toHaveLength(1); + }); + + it('should render the datasource and visualization panels inside the dimension popover', () => { + mockVisualization.getConfiguration.mockReturnValueOnce({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: ['newid'], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + enableDimensionEditor: true, + }, + ], + }); + mockVisualization.renderDimensionEditor = jest.fn(); + + const component = mountWithIntl(); + + const group = component.find('DimensionPopover'); + const panel = mount(group.prop('panel')); + + expect(panel.find('EuiTabbedContent').prop('tabs')).toHaveLength(2); + act(() => { + panel.find('EuiTab#visualization').simulate('click'); + }); + expect(mockVisualization.renderDimensionEditor).toHaveBeenCalledWith( + expect.any(Element), + expect.objectContaining({ + groupId: 'a', + accessor: 'newid', + }) + ); + }); + + it('should keep the popover open when configuring a new dimension', () => { + /** + * The ID generation system for new dimensions has been messy before, so + * this tests that the ID used in the first render is used to keep the popover + * open in future renders + */ + (generateId as jest.Mock).mockReturnValueOnce(`newid`); + (generateId as jest.Mock).mockReturnValueOnce(`bad`); + mockVisualization.getConfiguration.mockReturnValueOnce({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: [], + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + }, + ], + }); + // Normally the configuration would change in response to a state update, + // but this test is updating it directly + mockVisualization.getConfiguration.mockReturnValueOnce({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: ['newid'], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + ], + }); + + const component = mountWithIntl(); + + const group = component.find('DimensionPopover'); + const triggerButton = mountWithIntl(group.prop('trigger')); + act(() => { + triggerButton.find('[data-test-subj="lns-empty-dimension"]').first().simulate('click'); + }); + component.update(); + + expect(component.find(EuiPopover).prop('isOpen')).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 814b7fc644c9c..bd501db2b752a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -13,11 +13,12 @@ import { EuiFlexItem, EuiButtonEmpty, EuiFormRow, + EuiTabbedContent, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { NativeRenderer } from '../../../native_renderer'; -import { Visualization, FramePublicAPI, StateSetter } from '../../../types'; +import { StateSetter } from '../../../types'; import { DragContext, DragDrop, ChildDragDropProvider } from '../../../drag_drop'; import { LayerSettings } from './layer_settings'; import { trackUiEvent } from '../../../lens_ui_telemetry'; @@ -27,11 +28,8 @@ import { DimensionPopover } from './dimension_popover'; export function LayerPanel( props: Exclude & { - frame: FramePublicAPI; layerId: string; isOnlyLayer: boolean; - activeVisualization: Visualization; - visualizationState: unknown; updateVisualization: StateSetter; updateDatasource: (datasourceId: string, newState: unknown) => void; updateAll: ( @@ -47,13 +45,19 @@ export function LayerPanel( isOpen: false, openId: null, addingToGroupId: null, + tabId: null, }); - const { framePublicAPI, layerId, activeVisualization, isOnlyLayer, onRemoveLayer } = props; + const { framePublicAPI, layerId, isOnlyLayer, onRemoveLayer } = props; const datasourcePublicAPI = framePublicAPI.datasourceLayers[layerId]; - if (!datasourcePublicAPI) { + if ( + !datasourcePublicAPI || + !props.activeVisualizationId || + !props.visualizationMap[props.activeVisualizationId] + ) { return null; } + const activeVisualization = props.visualizationMap[props.activeVisualizationId]; const layerVisualizationConfigProps = { layerId, dragDropContext, @@ -158,104 +162,156 @@ export function LayerPanel( } > <> - {group.accessors.map((accessor) => ( - { - layerDatasource.onDrop({ - ...layerDatasourceDropProps, - droppedItem, - columnId: accessor, - filterOperations: group.filterOperations, - }); - }} - > - { - if (popoverState.isOpen) { - setPopoverState({ - isOpen: false, - openId: null, - addingToGroupId: null, - }); - } else { - setPopoverState({ - isOpen: true, - openId: accessor, - addingToGroupId: null, // not set for existing dimension - }); - } - }, - }} - /> - } - panel={ - - } - /> + {group.accessors.map((accessor) => { + const tabs = [ + { + id: 'datasource', + name: i18n.translate('xpack.lens.editorFrame.quickFunctionsLabel', { + defaultMessage: 'Quick functions', + }), + content: ( + <> + + + + ), + }, + ]; - { - trackUiEvent('indexpattern_dimension_removed'); - props.updateAll( - datasourceId, - layerDatasource.removeColumn({ - layerId, - columnId: accessor, - prevState: layerDatasourceState, - }), - props.activeVisualization.removeDimension({ - layerId, - columnId: accessor, - prevState: props.visualizationState, - }) - ); + if (activeVisualization.renderDimensionEditor) { + tabs.push({ + id: 'visualization', + name: i18n.translate('xpack.lens.editorFrame.formatStyleLabel', { + defaultMessage: 'Format & style', + }), + content: ( +
+ + +
+ ), + }); + } + + return ( + { + layerDatasource.onDrop({ + ...layerDatasourceDropProps, + droppedItem, + columnId: accessor, + filterOperations: group.filterOperations, + }); }} - /> - - ))} + > + { + if (popoverState.isOpen) { + setPopoverState({ + isOpen: false, + openId: null, + addingToGroupId: null, + tabId: null, + }); + } else { + setPopoverState({ + isOpen: true, + openId: accessor, + addingToGroupId: null, // not set for existing dimension + tabId: 'datasource', + }); + } + }, + }} + /> + } + panel={ + t.id === popoverState.tabId)} + size="s" + onTabClick={(tab) => { + setPopoverState({ + ...popoverState, + tabId: tab.id as typeof popoverState['tabId'], + }); + }} + /> + } + /> + + { + trackUiEvent('indexpattern_dimension_removed'); + props.updateAll( + datasourceId, + layerDatasource.removeColumn({ + layerId, + columnId: accessor, + prevState: layerDatasourceState, + }), + activeVisualization.removeDimension({ + layerId, + columnId: accessor, + prevState: props.visualizationState, + }) + ); + }} + /> +
+ ); + })} {group.supportsMoreColumns ? ( ) } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx index 6b0f0338d4015..fd509c0046e13 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx @@ -21,6 +21,7 @@ import { SuggestionPanel, SuggestionPanelProps } from './suggestion_panel'; import { getSuggestions, Suggestion } from './suggestion_helpers'; import { EuiIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; import chartTableSVG from '../../..assets/chart_datatable.svg'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; jest.mock('./suggestion_helpers'); @@ -85,6 +86,7 @@ describe('suggestion_panel', () => { dispatch: dispatchMock, ExpressionRenderer: expressionRendererMock, frame: createMockFramePublicAPI(), + plugins: { data: dataPluginMock.createStartContract() }, }; }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 0f0885d696ba4..b06b316ec79aa 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -24,10 +24,14 @@ import classNames from 'classnames'; import { Action, PreviewState } from './state_management'; import { Datasource, Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../types'; import { getSuggestions, switchToSuggestion } from './suggestion_helpers'; -import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; +import { + ReactExpressionRendererProps, + ReactExpressionRendererType, +} from '../../../../../../src/plugins/expressions/public'; import { prependDatasourceExpression, prependKibanaContext } from './expression_helpers'; import { debouncedComponent } from '../../debounced_component'; import { trackUiEvent, trackSuggestionEvent } from '../../lens_ui_telemetry'; +import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; const MAX_SUGGESTIONS_DISPLAYED = 5; @@ -52,6 +56,7 @@ export interface SuggestionPanelProps { ExpressionRenderer: ReactExpressionRendererType; frame: FramePublicAPI; stagedPreview?: PreviewState; + plugins: { data: DataPublicPluginStart }; } const PreviewRenderer = ({ @@ -154,6 +159,7 @@ export function SuggestionPanel({ frame, ExpressionRenderer: ExpressionRendererComponent, stagedPreview, + plugins, }: SuggestionPanelProps) { const currentDatasourceStates = stagedPreview ? stagedPreview.datasourceStates : datasourceStates; const currentVisualizationState = stagedPreview @@ -204,6 +210,13 @@ export function SuggestionPanel({ visualizationMap, ]); + const AutoRefreshExpressionRenderer = useMemo(() => { + const autoRefreshFetch$ = plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$(); + return (props: ReactExpressionRendererProps) => ( + + ); + }, [plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$, ExpressionRendererComponent]); + const [lastSelectedSuggestion, setLastSelectedSuggestion] = useState(-1); useEffect(() => { @@ -296,7 +309,7 @@ export function SuggestionPanel({ defaultMessage: 'Current', }), }} - ExpressionRenderer={ExpressionRendererComponent} + ExpressionRenderer={AutoRefreshExpressionRenderer} onSelect={rollbackToCurrentVisualization} selected={lastSelectedSuggestion === -1} showTitleAsLabel @@ -312,7 +325,7 @@ export function SuggestionPanel({ icon: suggestion.previewIcon, title: suggestion.title, }} - ExpressionRenderer={ExpressionRendererComponent} + ExpressionRenderer={AutoRefreshExpressionRenderer} key={index} onSelect={() => { trackUiEvent('suggestion_clicked'); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx index 59b5f358e190f..49d12e9f41440 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx @@ -21,11 +21,17 @@ import { ReactWrapper } from 'enzyme'; import { DragDrop, ChildDragDropProvider } from '../../drag_drop'; import { Ast } from '@kbn/interpreter/common'; import { coreMock } from 'src/core/public/mocks'; -import { esFilters, IFieldType, IIndexPattern } from '../../../../../../src/plugins/data/public'; +import { + DataPublicPluginStart, + esFilters, + IFieldType, + IIndexPattern, +} from '../../../../../../src/plugins/data/public'; import { TriggerId, UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { uiActionsPluginMock } from '../../../../../../src/plugins/ui_actions/public/mocks'; import { TriggerContract } from '../../../../../../src/plugins/ui_actions/public/triggers'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; describe('workspace_panel', () => { let mockVisualization: jest.Mocked; @@ -34,6 +40,7 @@ describe('workspace_panel', () => { let expressionRendererMock: jest.Mock; let uiActionsMock: jest.Mocked; + let dataMock: jest.Mocked; let trigger: jest.Mocked>; let instance: ReactWrapper; @@ -41,6 +48,7 @@ describe('workspace_panel', () => { beforeEach(() => { trigger = ({ exec: jest.fn() } as unknown) as jest.Mocked>; uiActionsMock = uiActionsPluginMock.createStartContract(); + dataMock = dataPluginMock.createStartContract(); uiActionsMock.getTrigger.mockReturnValue(trigger); mockVisualization = createMockVisualization(); mockVisualization2 = createMockVisualization(); @@ -69,7 +77,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -92,7 +100,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -115,7 +123,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -152,7 +160,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -240,7 +248,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -292,7 +300,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -372,7 +380,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -427,7 +435,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -482,7 +490,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -520,7 +528,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -564,7 +572,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -620,7 +628,7 @@ describe('workspace_panel', () => { dispatch={mockDispatch} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx index 44dd9f8364870..76da38ead6523 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx @@ -36,6 +36,7 @@ import { debouncedComponent } from '../../debounced_component'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public'; +import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; export interface WorkspacePanelProps { activeVisualizationId: string | null; @@ -54,7 +55,7 @@ export interface WorkspacePanelProps { dispatch: (action: Action) => void; ExpressionRenderer: ReactExpressionRendererType; core: CoreStart | CoreSetup; - plugins: { uiActions?: UiActionsStart }; + plugins: { uiActions?: UiActionsStart; data: DataPublicPluginStart }; } export const WorkspacePanel = debouncedComponent(InnerWorkspacePanel); @@ -135,6 +136,11 @@ export function InnerWorkspacePanel({ framePublicAPI.filters, ]); + const autoRefreshFetch$ = useMemo( + () => plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$(), + [plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$] + ); + useEffect(() => { // reset expression error if component attempts to run it again if (expression && localState.expressionBuildError) { @@ -224,6 +230,7 @@ export function InnerWorkspacePanel({ className="lnsExpressionRenderer__component" padding="m" expression={expression!} + reload$={autoRefreshFetch$} onEvent={(event: ExpressionRendererEvent) => { if (!plugins.uiActions) { // ui actions not available, not handling event... diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 42dcce0e438d7..c2437aa3cc3cc 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -290,6 +290,12 @@ export type VisualizationLayerWidgetProps = VisualizationConfigProp setState: (newState: T) => void; }; +export type VisualizationDimensionEditorProps = VisualizationConfigProps & { + groupId: string; + accessor: string; + setState: (newState: T) => void; +}; + export type VisualizationDimensionGroupConfig = SharedDimensionProps & { groupLabel: string; @@ -300,6 +306,12 @@ export type VisualizationDimensionGroupConfig = SharedDimensionProps & { /** If required, a warning will appear if accessors are empty */ required?: boolean; dataTestSubj?: string; + + /** + * When the dimension editor is enabled for this group, all dimensions in the group + * will render the extra tab for the dimension editor + */ + enableDimensionEditor?: boolean; }; interface VisualizationDimensionChangeProps { @@ -459,6 +471,15 @@ export interface Visualization { */ removeDimension: (props: VisualizationDimensionChangeProps) => T; + /** + * Additional editor that gets rendered inside the dimension popover. + * This can be used to configure dimension-specific options + */ + renderDimensionEditor?: ( + domElement: Element, + props: VisualizationDimensionEditorProps + ) => void; + /** * The frame will call this function on all visualizations at different times. The * main use cases where visualization suggestions are requested are: diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap index e4411807dfa56..28ce3c6c07501 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AddLicense component when license is active should display correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; +exports[`AddLicense component when license is active should display correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; -exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; +exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index b621e89efbee3..cc8cbfe679eff 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -112,6 +112,42 @@ exports[`UploadLicense should display a modal when license requires acknowledgem "refresh": [MockFunction], }, }, + "services": Object { + "history": Object { + "action": "PUSH", + "block": [MockFunction], + "createHref": [MockFunction] { + "calls": Array [ + Array [ + Object { + "pathname": "/home", + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": "/home", + }, + ], + }, + "createSubHistory": [MockFunction], + "go": [MockFunction], + "goBack": [MockFunction], + "goForward": [MockFunction], + "length": 1, + "listen": [MockFunction], + "location": Object { + "hash": "", + "key": undefined, + "pathname": "/", + "search": "", + "state": undefined, + }, + "push": [MockFunction], + "replace": [MockFunction], + }, + }, } } > @@ -126,12 +162,85 @@ exports[`UploadLicense should display a modal when license requires acknowledgem } } > - + @@ -1162,12 +1336,139 @@ exports[`UploadLicense should display an error when ES says license is expired 1 } } > - + @@ -1628,12 +1994,139 @@ exports[`UploadLicense should display an error when ES says license is invalid 1 } } > - + @@ -2094,12 +2652,139 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`] } } > - + @@ -2560,12 +3310,139 @@ exports[`UploadLicense should display error when ES returns error 1`] = ` } } > - + {}; let store: any = null; let component: any = null; +const history = scopedHistoryMock.create(); +history.createHref.mockImplementation((location: LocationDescriptorObject) => { + return `${location.pathname}${location.search ? '?' + location.search : ''}`; +}); const appDependencies = { plugins: { @@ -39,14 +44,15 @@ const appDependencies = { refresh: jest.fn(), }, }, + services: { + history, + }, docLinks: {}, }; const thunkServices = { http: httpServiceMock.createSetupContract(), - history: { - replace: jest.fn(), - }, + history, breadcrumbService: { setBreadcrumbs() {}, }, @@ -59,7 +65,7 @@ describe('UploadLicense', () => { component = ( - + ); diff --git a/x-pack/plugins/license_management/__jest__/util/util.js b/x-pack/plugins/license_management/__jest__/util/util.js index 5a7e49c8c3315..c13dcdb7fdbfa 100644 --- a/x-pack/plugins/license_management/__jest__/util/util.js +++ b/x-pack/plugins/license_management/__jest__/util/util.js @@ -9,14 +9,22 @@ import React from 'react'; import { Provider } from 'react-redux'; import { mountWithIntl } from '../../../../test_utils/enzyme_helpers'; -import { httpServiceMock } from '../../../../../src/core/public/mocks'; +import { httpServiceMock, scopedHistoryMock } from '../../../../../src/core/public/mocks'; import { licenseManagementStore } from '../../public/application/store/store'; import { AppContextProvider } from '../../public/application/app_context'; const highExpirationMillis = new Date('October 13, 2099 00:00:00Z').getTime(); +const history = scopedHistoryMock.create(); +history.createHref.mockImplementation((location) => { + return `${location.pathname}${location.search ? '?' + location.search : ''}`; +}); + const appDependencies = { docLinks: {}, + services: { + history, + }, }; export const createMockLicense = (type, expiryDateInMillis = highExpirationMillis) => { @@ -30,6 +38,7 @@ export const createMockLicense = (type, expiryDateInMillis = highExpirationMilli export const getComponent = (initialState, Component) => { const services = { http: httpServiceMock.createSetupContract(), + history, }; const store = licenseManagementStore(initialState, services); return mountWithIntl( diff --git a/x-pack/plugins/license_management/common/constants/base_path.ts b/x-pack/plugins/license_management/common/constants/base_path.ts index 6ed03a0428096..ffb470e931921 100644 --- a/x-pack/plugins/license_management/common/constants/base_path.ts +++ b/x-pack/plugins/license_management/common/constants/base_path.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const BASE_PATH = '/management/stack/license_management/'; - export const API_BASE_PATH = '/api/license'; diff --git a/x-pack/plugins/license_management/common/constants/index.ts b/x-pack/plugins/license_management/common/constants/index.ts index ec411fea4b7a9..a531ba08401f8 100644 --- a/x-pack/plugins/license_management/common/constants/index.ts +++ b/x-pack/plugins/license_management/common/constants/index.ts @@ -5,6 +5,6 @@ */ export { PLUGIN } from './plugin'; -export { BASE_PATH, API_BASE_PATH } from './base_path'; +export { API_BASE_PATH } from './base_path'; export { EXTERNAL_LINKS } from './external_links'; export { APP_PERMISSION } from './permissions'; diff --git a/x-pack/plugins/license_management/public/application/app.js b/x-pack/plugins/license_management/public/application/app.js index 46d0da5252ceb..6885a249be01c 100644 --- a/x-pack/plugins/license_management/public/application/app.js +++ b/x-pack/plugins/license_management/public/application/app.js @@ -8,7 +8,7 @@ import React, { Component } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { LicenseDashboard, UploadLicense } from './sections'; import { Switch, Route } from 'react-router-dom'; -import { APP_PERMISSION, BASE_PATH } from '../../common/constants'; +import { APP_PERMISSION } from '../../common/constants'; import { EuiPageBody, EuiEmptyPrompt, EuiText, EuiLoadingSpinner, EuiCallOut } from '@elastic/eui'; export class App extends Component { @@ -89,8 +89,8 @@ export class App extends Component { return ( - - + + ); diff --git a/x-pack/plugins/license_management/public/application/app_context.tsx b/x-pack/plugins/license_management/public/application/app_context.tsx index 1e90f4c907b8c..39e7ef5f16e79 100644 --- a/x-pack/plugins/license_management/public/application/app_context.tsx +++ b/x-pack/plugins/license_management/public/application/app_context.tsx @@ -5,6 +5,7 @@ */ import React, { createContext, useContext } from 'react'; +import { ScopedHistory } from 'kibana/public'; import { CoreStart } from '../../../../../src/core/public'; import { LicensingPluginSetup, ILicense } from '../../../licensing/public'; @@ -18,6 +19,7 @@ export interface AppDependencies { core: CoreStart; services: { breadcrumbService: BreadcrumbService; + history: ScopedHistory; }; plugins: { licensing: LicensingPluginSetup; diff --git a/x-pack/plugins/license_management/public/application/app_providers.tsx b/x-pack/plugins/license_management/public/application/app_providers.tsx index 9f9fd2a8275df..139290f2c46ce 100644 --- a/x-pack/plugins/license_management/public/application/app_providers.tsx +++ b/x-pack/plugins/license_management/public/application/app_providers.tsx @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import * as history from 'history'; import { Provider } from 'react-redux'; -import { BASE_PATH } from '../../common/constants'; import { AppContextProvider, AppDependencies } from './app_context'; // @ts-ignore import { licenseManagementStore } from './store'; @@ -33,8 +31,7 @@ export const AppProviders = ({ appDependencies, children }: Props) => { // Setup Redux store const thunkServices = { - // So we can imperatively control the hash route - history: history.createHashHistory({ basename: BASE_PATH }), + history: appDependencies.services.history, toasts, http, telemetry: plugins.telemetry, diff --git a/x-pack/plugins/license_management/public/application/breadcrumbs.ts b/x-pack/plugins/license_management/public/application/breadcrumbs.ts index b1773a10f01ba..d3a69f55c4347 100644 --- a/x-pack/plugins/license_management/public/application/breadcrumbs.ts +++ b/x-pack/plugins/license_management/public/application/breadcrumbs.ts @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { ManagementAppMountParams } from '../../../../../src/plugins/management/public'; -import { BASE_PATH } from '../../common/constants'; type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; @@ -32,7 +31,7 @@ export class BreadcrumbService { text: i18n.translate('xpack.licenseMgmt.dashboard.breadcrumb', { defaultMessage: 'License management', }), - href: `#${BASE_PATH}home`, + href: `/`, }, ]; diff --git a/x-pack/plugins/license_management/public/application/index.tsx b/x-pack/plugins/license_management/public/application/index.tsx index 75f2f98f51e6e..cca164b14b8b8 100644 --- a/x-pack/plugins/license_management/public/application/index.tsx +++ b/x-pack/plugins/license_management/public/application/index.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter } from 'react-router-dom'; +import { Router } from 'react-router-dom'; import { AppDependencies } from './app_context'; import { AppProviders } from './app_providers'; @@ -14,15 +14,18 @@ import { AppProviders } from './app_providers'; import { App } from './app.container'; const AppWithRouter = (props: { [key: string]: any }) => ( - + - + ); export const renderApp = (element: Element, dependencies: AppDependencies) => { render( - + , element ); diff --git a/x-pack/plugins/license_management/public/application/sections/license_dashboard/add_license/add_license.js b/x-pack/plugins/license_management/public/application/sections/license_dashboard/add_license/add_license.js index 158702e1286ae..d13a3bc34a7f2 100644 --- a/x-pack/plugins/license_management/public/application/sections/license_dashboard/add_license/add_license.js +++ b/x-pack/plugins/license_management/public/application/sections/license_dashboard/add_license/add_license.js @@ -5,12 +5,16 @@ */ import React from 'react'; -import { BASE_PATH } from '../../../../../common/constants'; import { EuiCard, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useAppContext } from '../../../app_context'; + +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + +export const AddLicense = ({ uploadPath = `/upload_license` }) => { + const { services } = useAppContext(); -export const AddLicense = ({ uploadPath = `#${BASE_PATH}upload_license` }) => { return ( { /> } footer={ - + @@ -189,7 +190,7 @@ export class UploadLicense extends React.PureComponent { - + { + mount: async ({ element, setBreadcrumbs, history }) => { const [core] = await getStartServices(); const initialLicense = await plugins.licensing.license$.pipe(first()).toPromise(); @@ -72,6 +72,7 @@ export class LicenseManagementUIPlugin }, services: { breadcrumbService: this.breadcrumbService, + history, }, store: { initialLicense, diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index 564f2fdb21116..960fe3699e210 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -374,7 +374,7 @@ describe('licensing plugin', () => { expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1); expect(mountExpiredBannerMock).toHaveBeenCalledWith({ type: 'gold', - uploadUrl: '/app/kibana#/management/stack/license_management/upload_license', + uploadUrl: '/app/management/stack/license_management/upload_license', }); }); }); diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index c39acb12b06e1..ec42a73f610c0 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -161,7 +161,7 @@ export class LicensingPlugin implements Plugin { const [, , { featureUsage }] = await getStartServices(); return response.ok({ - body: [...featureUsage.getLastUsages().entries()].reduce( - (res, [featureName, lastUsage]) => { - return { - ...res, - [featureName]: new Date(lastUsage).toISOString(), - }; - }, - {} - ), + body: { + features: featureUsage.getLastUsages().map((usage) => ({ + name: usage.name, + last_used: usage.lastUsed, + license_level: usage.licenseType, + })), + }, }); } ); diff --git a/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts b/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts index f0ef0dbec0b22..39f7aa6503b35 100644 --- a/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts +++ b/x-pack/plugins/licensing/server/services/feature_usage_service.test.ts @@ -17,16 +17,13 @@ describe('FeatureUsageService', () => { jest.restoreAllMocks(); }); - const toObj = (map: ReadonlyMap): Record => - Object.fromEntries(map.entries()); - describe('#setup', () => { describe('#register', () => { it('throws when registering the same feature twice', () => { const setup = service.setup(); - setup.register('foo'); + setup.register('foo', 'basic'); expect(() => { - setup.register('foo'); + setup.register('foo', 'basic'); }).toThrowErrorMatchingInlineSnapshot(`"Feature 'foo' has already been registered."`); }); }); @@ -36,32 +33,50 @@ describe('FeatureUsageService', () => { describe('#notifyUsage', () => { it('allows to notify a feature usage', () => { const setup = service.setup(); - setup.register('feature'); + setup.register('feature', 'basic'); const start = service.start(); start.notifyUsage('feature', 127001); - expect(start.getLastUsages().get('feature')).toBe(127001); + expect(start.getLastUsages()).toEqual([ + { + lastUsed: new Date(127001), + licenseType: 'basic', + name: 'feature', + }, + ]); }); it('can receive a Date object', () => { const setup = service.setup(); - setup.register('feature'); + setup.register('feature', 'basic'); const start = service.start(); const usageTime = new Date(2015, 9, 21, 17, 54, 12); start.notifyUsage('feature', usageTime); - expect(start.getLastUsages().get('feature')).toBe(usageTime.getTime()); + expect(start.getLastUsages()).toEqual([ + { + lastUsed: usageTime, + licenseType: 'basic', + name: 'feature', + }, + ]); }); it('uses the current time when `usedAt` is unspecified', () => { jest.spyOn(Date, 'now').mockReturnValue(42); const setup = service.setup(); - setup.register('feature'); + setup.register('feature', 'basic'); const start = service.start(); start.notifyUsage('feature'); - expect(start.getLastUsages().get('feature')).toBe(42); + expect(start.getLastUsages()).toEqual([ + { + lastUsed: new Date(42), + licenseType: 'basic', + name: 'feature', + }, + ]); }); it('throws when notifying for an unregistered feature', () => { @@ -76,40 +91,41 @@ describe('FeatureUsageService', () => { describe('#getLastUsages', () => { it('returns the last usage for all used features', () => { const setup = service.setup(); - setup.register('featureA'); - setup.register('featureB'); + setup.register('featureA', 'basic'); + setup.register('featureB', 'gold'); const start = service.start(); start.notifyUsage('featureA', 127001); start.notifyUsage('featureB', 6666); - expect(toObj(start.getLastUsages())).toEqual({ - featureA: 127001, - featureB: 6666, - }); + expect(start.getLastUsages()).toEqual([ + { lastUsed: new Date(127001), licenseType: 'basic', name: 'featureA' }, + { lastUsed: new Date(6666), licenseType: 'gold', name: 'featureB' }, + ]); }); it('returns the last usage even after notifying for an older usage', () => { const setup = service.setup(); - setup.register('featureA'); + setup.register('featureA', 'basic'); const start = service.start(); start.notifyUsage('featureA', 1000); start.notifyUsage('featureA', 500); - expect(toObj(start.getLastUsages())).toEqual({ - featureA: 1000, - }); + expect(start.getLastUsages()).toEqual([ + { lastUsed: new Date(1000), licenseType: 'basic', name: 'featureA' }, + ]); }); - it('does not return entries for unused registered features', () => { + it('returns entries for unused registered features', () => { const setup = service.setup(); - setup.register('featureA'); - setup.register('featureB'); + setup.register('featureA', 'basic'); + setup.register('featureB', 'gold'); const start = service.start(); start.notifyUsage('featureA', 127001); - expect(toObj(start.getLastUsages())).toEqual({ - featureA: 127001, - }); + expect(start.getLastUsages()).toEqual([ + { lastUsed: new Date(127001), licenseType: 'basic', name: 'featureA' }, + { lastUsed: null, licenseType: 'gold', name: 'featureB' }, + ]); }); }); }); diff --git a/x-pack/plugins/licensing/server/services/feature_usage_service.ts b/x-pack/plugins/licensing/server/services/feature_usage_service.ts index 0c6613d37f63a..9bfcb28f36b2a 100644 --- a/x-pack/plugins/licensing/server/services/feature_usage_service.ts +++ b/x-pack/plugins/licensing/server/services/feature_usage_service.ts @@ -5,13 +5,20 @@ */ import { isDate } from 'lodash'; +import { LicenseType } from '../../common/types'; /** @public */ export interface FeatureUsageServiceSetup { /** * Register a feature to be able to notify of it's usages using the {@link FeatureUsageServiceStart | service start contract}. */ - register(featureName: string): void; + register(featureName: string, licenseType: LicenseType): void; +} + +export interface LastFeatureUsage { + name: string; + lastUsed: Date | null; + licenseType: LicenseType; } /** @public */ @@ -27,20 +34,23 @@ export interface FeatureUsageServiceStart { * Return a map containing last usage timestamp for all features. * Features that were not used yet do not appear in the map. */ - getLastUsages(): ReadonlyMap; + getLastUsages(): LastFeatureUsage[]; } export class FeatureUsageService { - private readonly features: string[] = []; - private readonly lastUsages = new Map(); + private readonly lastUsages = new Map(); public setup(): FeatureUsageServiceSetup { return { - register: (featureName) => { - if (this.features.includes(featureName)) { + register: (featureName, licenseType) => { + if (this.lastUsages.has(featureName)) { throw new Error(`Feature '${featureName}' has already been registered.`); } - this.features.push(featureName); + this.lastUsages.set(featureName, { + name: featureName, + lastUsed: null, + licenseType, + }); }, }; } @@ -48,16 +58,17 @@ export class FeatureUsageService { public start(): FeatureUsageServiceStart { return { notifyUsage: (featureName, usedAt = Date.now()) => { - if (!this.features.includes(featureName)) { + const usage = this.lastUsages.get(featureName); + if (!usage) { throw new Error(`Feature '${featureName}' is not registered.`); } - if (isDate(usedAt)) { - usedAt = usedAt.getTime(); + + const lastUsed = isDate(usedAt) ? usedAt : new Date(usedAt); + if (usage.lastUsed == null || lastUsed > usage.lastUsed) { + usage.lastUsed = lastUsed; } - const currentValue = this.lastUsages.get(featureName) ?? 0; - this.lastUsages.set(featureName, Math.max(usedAt, currentValue)); }, - getLastUsages: () => new Map(this.lastUsages.entries()), + getLastUsages: () => Array.from(this.lastUsages.values()), }; } } diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts index 8c5f6b0cbe56c..d8e4dfba1599e 100644 --- a/x-pack/plugins/lists/common/constants.mock.ts +++ b/x-pack/plugins/lists/common/constants.mock.ts @@ -29,3 +29,13 @@ export const TYPE = 'ip'; export const VALUE = '127.0.0.1'; export const VALUE_2 = '255.255.255'; export const NAMESPACE_TYPE = 'single'; + +// Exception List specific +export const ENDPOINT_TYPE = 'endpoint'; +export const ENTRIES = [ + { field: 'some.field', match: 'some value', match_any: undefined, operator: 'included' }, +]; +export const ITEM_TYPE = 'simple'; +export const _TAGS = []; +export const TAGS = []; +export const COMMENT = []; diff --git a/x-pack/plugins/lists/common/constants.ts b/x-pack/plugins/lists/common/constants.ts index 96d28bf618ce4..6cb88b19483ce 100644 --- a/x-pack/plugins/lists/common/constants.ts +++ b/x-pack/plugins/lists/common/constants.ts @@ -16,3 +16,9 @@ export const LIST_ITEM_URL = `${LIST_URL}/items`; */ export const EXCEPTION_LIST_URL = '/api/exception_lists'; export const EXCEPTION_LIST_ITEM_URL = '/api/exception_lists/items'; + +/** + * Exception list spaces + */ +export const EXCEPTION_LIST_NAMESPACE_AGNOSTIC = 'exception-list-agnostic'; +export const EXCEPTION_LIST_NAMESPACE = 'exception-list'; diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts new file mode 100644 index 0000000000000..f9af10245b7ee --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts @@ -0,0 +1,34 @@ +/* + * 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 { + COMMENT, + DESCRIPTION, + ENTRIES, + ITEM_TYPE, + LIST_ID, + META, + NAME, + NAMESPACE_TYPE, + TAGS, + _TAGS, +} from '../../constants.mock'; + +import { CreateExceptionListItemSchema } from './create_exception_list_item_schema'; + +export const getCreateExceptionListItemSchemaMock = (): CreateExceptionListItemSchema => ({ + _tags: _TAGS, + comment: COMMENT, + description: DESCRIPTION, + entries: ENTRIES, + item_id: undefined, + list_id: LIST_ID, + meta: META, + name: NAME, + namespace_type: NAMESPACE_TYPE, + tags: TAGS, + type: ITEM_TYPE, +}); diff --git a/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts b/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts index 787d374ab2cad..ecc771279b3ab 100644 --- a/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts +++ b/x-pack/plugins/lists/public/exceptions/__mocks__/api.ts @@ -40,8 +40,10 @@ export const fetchExceptionListById = async ({ }: ApiCallByIdProps): Promise => Promise.resolve(getExceptionListSchemaMock()); export const fetchExceptionListItemsByListId = async ({ + filterOptions, http, listId, + pagination, signal, }: ApiCallByListIdProps): Promise => Promise.resolve({ data: [getExceptionListItemSchemaMock()], page: 1, per_page: 20, total: 1 }); diff --git a/x-pack/plugins/lists/public/exceptions/api.test.ts b/x-pack/plugins/lists/public/exceptions/api.test.ts index 18a89071e9887..b9512bb398745 100644 --- a/x-pack/plugins/lists/public/exceptions/api.test.ts +++ b/x-pack/plugins/lists/public/exceptions/api.test.ts @@ -6,8 +6,9 @@ import { createKibanaCoreStartMock } from '../common/mocks/kibana_core'; import { getExceptionListSchemaMock } from '../../common/schemas/response/exception_list_schema.mock'; import { getExceptionListItemSchemaMock } from '../../common/schemas/response/exception_list_item_schema.mock'; +import { getCreateExceptionListSchemaMock } from '../../common/schemas/request/create_exception_list_schema.mock'; +import { getCreateExceptionListItemSchemaMock } from '../../common/schemas/request/create_exception_list_item_schema.mock'; -import { mockNewExceptionItem, mockNewExceptionList } from './mock'; import { addExceptionList, addExceptionListItem, @@ -37,188 +38,291 @@ const mockKibanaHttpService = ((createKibanaCoreStartMock() as unknown) as jest. ); describe('Exceptions Lists API', () => { - describe('addExceptionList', () => { + describe('#addExceptionList', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(getExceptionListSchemaMock()); }); - test('check parameter url, body', async () => { - await addExceptionList({ + test('it uses POST when "list.id" does not exist', async () => { + const payload = getCreateExceptionListSchemaMock(); + const exceptionResponse = await addExceptionList({ http: mockKibanaHttpService(), - list: mockNewExceptionList, + list: payload, signal: abortCtrl.signal, }); + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists', { - body: - '{"_tags":["endpoint","process","malware","os:linux"],"description":"This is a sample endpoint type exception","list_id":"endpoint_list","name":"Sample Endpoint Exception List","tags":["user added string for a tag","malware"],"type":"endpoint"}', + body: JSON.stringify(payload), method: 'POST', signal: abortCtrl.signal, }); + expect(exceptionResponse).toEqual({ id: '1', ...getExceptionListSchemaMock() }); }); - test('check parameter url, body when "list.id" exists', async () => { - await addExceptionList({ + test('it uses PUT when "list.id" exists', async () => { + const payload = getExceptionListSchemaMock(); + const exceptionResponse = await addExceptionList({ http: mockKibanaHttpService(), list: getExceptionListSchemaMock(), signal: abortCtrl.signal, }); + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists', { - body: - '{"_tags":["endpoint","process","malware","os:linux"],"created_at":"2020-04-23T00:19:13.289Z","created_by":"user_name","description":"This is a sample endpoint type exception","id":"1","list_id":"endpoint_list","meta":{},"name":"Sample Endpoint Exception List","namespace_type":"single","tags":["user added string for a tag","malware"],"tie_breaker_id":"77fd1909-6786-428a-a671-30229a719c1f","type":"endpoint","updated_at":"2020-04-23T00:19:13.289Z","updated_by":"user_name"}', + body: JSON.stringify(payload), method: 'PUT', signal: abortCtrl.signal, }); - }); - - test('happy path', async () => { - const exceptionResponse = await addExceptionList({ - http: mockKibanaHttpService(), - list: mockNewExceptionList, - signal: abortCtrl.signal, - }); expect(exceptionResponse).toEqual(getExceptionListSchemaMock()); }); }); - describe('addExceptionListItem', () => { + describe('#addExceptionListItem', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(getExceptionListItemSchemaMock()); }); - test('check parameter url, body', async () => { - await addExceptionListItem({ + test('it uses POST when "listItem.id" does not exist', async () => { + const payload = getCreateExceptionListItemSchemaMock(); + const exceptionResponse = await addExceptionListItem({ http: mockKibanaHttpService(), - listItem: mockNewExceptionItem, + listItem: payload, signal: abortCtrl.signal, }); + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items', { - body: - '{"_tags":["endpoint","process","malware","os:linux"],"description":"This is a sample endpoint type exception","entries":[{"field":"actingProcess.file.signer","match":"Elastic, N.V.","operator":"included"},{"field":"event.category","match_any":["process","malware"],"operator":"included"}],"item_id":"endpoint_list_item","list_id":"endpoint_list","name":"Sample Endpoint Exception List","tags":["user added string for a tag","malware"],"type":"simple"}', + body: JSON.stringify(payload), method: 'POST', signal: abortCtrl.signal, }); + expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock()); }); test('check parameter url, body when "listItem.id" exists', async () => { - await addExceptionListItem({ + const payload = getExceptionListItemSchemaMock(); + const exceptionResponse = await addExceptionListItem({ http: mockKibanaHttpService(), listItem: getExceptionListItemSchemaMock(), signal: abortCtrl.signal, }); + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items', { - body: - '{"_tags":["endpoint","process","malware","os:linux"],"comment":[],"created_at":"2020-04-23T00:19:13.289Z","created_by":"user_name","description":"This is a sample endpoint type exception","entries":[{"field":"actingProcess.file.signer","match":"Elastic, N.V.","operator":"included"},{"field":"event.category","match_any":["process","malware"],"operator":"included"}],"id":"1","item_id":"endpoint_list_item","list_id":"endpoint_list","meta":{},"name":"Sample Endpoint Exception List","namespace_type":"single","tags":["user added string for a tag","malware"],"tie_breaker_id":"77fd1909-6786-428a-a671-30229a719c1f","type":"simple","updated_at":"2020-04-23T00:19:13.289Z","updated_by":"user_name"}', + body: JSON.stringify(payload), method: 'PUT', signal: abortCtrl.signal, }); - }); - - test('happy path', async () => { - const exceptionResponse = await addExceptionListItem({ - http: mockKibanaHttpService(), - listItem: mockNewExceptionItem, - signal: abortCtrl.signal, - }); expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock()); }); }); - describe('fetchExceptionListById', () => { + describe('#fetchExceptionListById', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(getExceptionListSchemaMock()); }); - test('check parameter url, body', async () => { + test('it invokes "fetchExceptionListById" with expected url and body values', async () => { await fetchExceptionListById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists', { method: 'GET', query: { id: '1', + namespace_type: 'single', }, signal: abortCtrl.signal, }); }); - test('happy path', async () => { + test('it returns expected exception list on success', async () => { const exceptionResponse = await fetchExceptionListById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(exceptionResponse).toEqual(getExceptionListSchemaMock()); }); }); - describe('fetchExceptionListItemsByListId', () => { + describe('#fetchExceptionListItemsByListId', () => { beforeEach(() => { fetchMock.mockClear(); - fetchMock.mockResolvedValue([mockNewExceptionItem]); + fetchMock.mockResolvedValue([getExceptionListItemSchemaMock()]); }); - test('check parameter url, body', async () => { + test('it invokes "fetchExceptionListItemsByListId" with expected url and body values', async () => { await fetchExceptionListItemsByListId({ http: mockKibanaHttpService(), - listId: 'endpoint_list', + listId: 'myList', + namespaceType: 'single', + signal: abortCtrl.signal, + }); + + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + method: 'GET', + query: { + list_id: 'myList', + namespace_type: 'single', + page: 1, + per_page: 20, + }, + signal: abortCtrl.signal, + }); + }); + + test('it invokes with expected url and body values when a filter exists and "namespaceType" of "single"', async () => { + await fetchExceptionListItemsByListId({ + filterOptions: { + filter: 'hello world', + tags: [], + }, + http: mockKibanaHttpService(), + listId: 'myList', + namespaceType: 'single', signal: abortCtrl.signal, }); + + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + method: 'GET', + query: { + filter: 'exception-list.attributes.entries.field:hello world*', + list_id: 'myList', + namespace_type: 'single', + page: 1, + per_page: 20, + }, + signal: abortCtrl.signal, + }); + }); + + test('it invokes with expected url and body values when a filter exists and "namespaceType" of "agnostic"', async () => { + await fetchExceptionListItemsByListId({ + filterOptions: { + filter: 'hello world', + tags: [], + }, + http: mockKibanaHttpService(), + listId: 'myList', + namespaceType: 'agnostic', + signal: abortCtrl.signal, + }); + + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + method: 'GET', + query: { + filter: 'exception-list-agnostic.attributes.entries.field:hello world*', + list_id: 'myList', + namespace_type: 'agnostic', + page: 1, + per_page: 20, + }, + signal: abortCtrl.signal, + }); + }); + + test('it invokes with expected url and body values when tags exists', async () => { + await fetchExceptionListItemsByListId({ + filterOptions: { + filter: '', + tags: ['malware'], + }, + http: mockKibanaHttpService(), + listId: 'myList', + namespaceType: 'agnostic', + signal: abortCtrl.signal, + }); + + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items/_find', { + method: 'GET', + query: { + filter: 'exception-list-agnostic.attributes.tags:malware', + list_id: 'myList', + namespace_type: 'agnostic', + page: 1, + per_page: 20, + }, + signal: abortCtrl.signal, + }); + }); + + test('it invokes with expected url and body values when filter and tags exists', async () => { + await fetchExceptionListItemsByListId({ + filterOptions: { + filter: 'host.name', + tags: ['malware'], + }, + http: mockKibanaHttpService(), + listId: 'myList', + namespaceType: 'agnostic', + signal: abortCtrl.signal, + }); + expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items/_find', { method: 'GET', query: { - list_id: 'endpoint_list', + filter: + 'exception-list-agnostic.attributes.entries.field:host.name* AND exception-list-agnostic.attributes.tags:malware', + list_id: 'myList', + namespace_type: 'agnostic', + page: 1, + per_page: 20, }, signal: abortCtrl.signal, }); }); - test('happy path', async () => { + test('it returns expected format when call succeeds', async () => { const exceptionResponse = await fetchExceptionListItemsByListId({ http: mockKibanaHttpService(), listId: 'endpoint_list', + namespaceType: 'single', signal: abortCtrl.signal, }); - expect(exceptionResponse).toEqual([mockNewExceptionItem]); + expect(exceptionResponse).toEqual([getExceptionListItemSchemaMock()]); }); }); - describe('fetchExceptionListItemById', () => { + describe('#fetchExceptionListItemById', () => { beforeEach(() => { fetchMock.mockClear(); - fetchMock.mockResolvedValue([mockNewExceptionItem]); + fetchMock.mockResolvedValue([getExceptionListItemSchemaMock()]); }); - test('check parameter url, body', async () => { + test('it invokes "fetchExceptionListItemById" with expected url and body values', async () => { await fetchExceptionListItemById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items', { method: 'GET', query: { id: '1', + namespace_type: 'single', }, signal: abortCtrl.signal, }); }); - test('happy path', async () => { + test('it returns expected format when call succeeds', async () => { const exceptionResponse = await fetchExceptionListItemById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); - expect(exceptionResponse).toEqual([mockNewExceptionItem]); + expect(exceptionResponse).toEqual([getExceptionListItemSchemaMock()]); }); }); - describe('deleteExceptionListById', () => { + describe('#deleteExceptionListById', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(getExceptionListSchemaMock()); @@ -228,28 +332,31 @@ describe('Exceptions Lists API', () => { await deleteExceptionListById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists', { method: 'DELETE', query: { id: '1', + namespace_type: 'single', }, signal: abortCtrl.signal, }); }); - test('happy path', async () => { + test('it returns expected format when call succeeds', async () => { const exceptionResponse = await deleteExceptionListById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(exceptionResponse).toEqual(getExceptionListSchemaMock()); }); }); - describe('deleteExceptionListItemById', () => { + describe('#deleteExceptionListItemById', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue(getExceptionListItemSchemaMock()); @@ -259,21 +366,24 @@ describe('Exceptions Lists API', () => { await deleteExceptionListItemById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items', { method: 'DELETE', query: { id: '1', + namespace_type: 'single', }, signal: abortCtrl.signal, }); }); - test('happy path', async () => { + test('it returns expected format when call succeeds', async () => { const exceptionResponse = await deleteExceptionListItemById({ http: mockKibanaHttpService(), id: '1', + namespaceType: 'single', signal: abortCtrl.signal, }); expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock()); diff --git a/x-pack/plugins/lists/public/exceptions/api.ts b/x-pack/plugins/lists/public/exceptions/api.ts index fdd9d62539e06..6968ba5f50e72 100644 --- a/x-pack/plugins/lists/public/exceptions/api.ts +++ b/x-pack/plugins/lists/public/exceptions/api.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '../../common/constants'; +import { + EXCEPTION_LIST_ITEM_URL, + EXCEPTION_LIST_NAMESPACE, + EXCEPTION_LIST_NAMESPACE_AGNOSTIC, + EXCEPTION_LIST_URL, +} from '../../common/constants'; import { ExceptionListItemSchema, ExceptionListSchema, @@ -21,6 +26,7 @@ import { /** * Add provided ExceptionList * + * @param http Kibana http service * @param list exception list to add * @param signal to cancel request * @@ -43,6 +49,7 @@ export const addExceptionList = async ({ /** * Add provided ExceptionListItem * + * @param http Kibana http service * @param listItem exception list item to add * @param signal to cancel request * @@ -65,7 +72,9 @@ export const addExceptionListItem = async ({ /** * Fetch an ExceptionList by providing a ExceptionList ID * + * @param http Kibana http service * @param id ExceptionList ID (not list_id) + * @param namespaceType ExceptionList namespace_type * @param signal to cancel request * * @throws An error if response is not OK @@ -73,18 +82,23 @@ export const addExceptionListItem = async ({ export const fetchExceptionListById = async ({ http, id, + namespaceType, signal, }: ApiCallByIdProps): Promise => http.fetch(`${EXCEPTION_LIST_URL}`, { method: 'GET', - query: { id }, + query: { id, namespace_type: namespaceType }, signal, }); /** * Fetch an ExceptionList's ExceptionItems by providing a ExceptionList list_id * - * @param id ExceptionList list_id (not ID) + * @param http Kibana http service + * @param listId ExceptionList list_id (not ID) + * @param namespaceType ExceptionList namespace_type + * @param filterOptions optional - filter by field or tags + * @param pagination optional * @param signal to cancel request * * @throws An error if response is not OK @@ -92,18 +106,48 @@ export const fetchExceptionListById = async ({ export const fetchExceptionListItemsByListId = async ({ http, listId, + namespaceType, + filterOptions = { + filter: '', + tags: [], + }, + pagination = { + page: 1, + perPage: 20, + total: 0, + }, signal, -}: ApiCallByListIdProps): Promise => - http.fetch(`${EXCEPTION_LIST_ITEM_URL}/_find`, { +}: ApiCallByListIdProps): Promise => { + const namespace = + namespaceType === 'agnostic' ? EXCEPTION_LIST_NAMESPACE_AGNOSTIC : EXCEPTION_LIST_NAMESPACE; + const filters = [ + ...(filterOptions.filter.length + ? [`${namespace}.attributes.entries.field:${filterOptions.filter}*`] + : []), + ...(filterOptions.tags?.map((t) => `${namespace}.attributes.tags:${t}`) ?? []), + ]; + + const query = { + list_id: listId, + namespace_type: namespaceType, + page: pagination.page, + per_page: pagination.perPage, + ...(filters.length ? { filter: filters.join(' AND ') } : {}), + }; + + return http.fetch(`${EXCEPTION_LIST_ITEM_URL}/_find`, { method: 'GET', - query: { list_id: listId }, + query, signal, }); +}; /** * Fetch an ExceptionListItem by providing a ExceptionListItem ID * + * @param http Kibana http service * @param id ExceptionListItem ID (not item_id) + * @param namespaceType ExceptionList namespace_type * @param signal to cancel request * * @throws An error if response is not OK @@ -111,18 +155,21 @@ export const fetchExceptionListItemsByListId = async ({ export const fetchExceptionListItemById = async ({ http, id, + namespaceType, signal, }: ApiCallByIdProps): Promise => http.fetch(`${EXCEPTION_LIST_ITEM_URL}`, { method: 'GET', - query: { id }, + query: { id, namespace_type: namespaceType }, signal, }); /** * Delete an ExceptionList by providing a ExceptionList ID * + * @param http Kibana http service * @param id ExceptionList ID (not list_id) + * @param namespaceType ExceptionList namespace_type * @param signal to cancel request * * @throws An error if response is not OK @@ -130,18 +177,21 @@ export const fetchExceptionListItemById = async ({ export const deleteExceptionListById = async ({ http, id, + namespaceType, signal, }: ApiCallByIdProps): Promise => http.fetch(`${EXCEPTION_LIST_URL}`, { method: 'DELETE', - query: { id }, + query: { id, namespace_type: namespaceType }, signal, }); /** * Delete an ExceptionListItem by providing a ExceptionListItem ID * + * @param http Kibana http service * @param id ExceptionListItem ID (not item_id) + * @param namespaceType ExceptionList namespace_type * @param signal to cancel request * * @throws An error if response is not OK @@ -149,10 +199,11 @@ export const deleteExceptionListById = async ({ export const deleteExceptionListItemById = async ({ http, id, + namespaceType, signal, }: ApiCallByIdProps): Promise => http.fetch(`${EXCEPTION_LIST_ITEM_URL}`, { method: 'DELETE', - query: { id }, + query: { id, namespace_type: namespaceType }, signal, }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.tsx b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.tsx index b78ad250b8910..1db18168b11fe 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.tsx @@ -6,8 +6,10 @@ import { act, renderHook } from '@testing-library/react-hooks'; +import * as api from '../api'; import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock'; import { createKibanaCoreStartMock } from '../../common/mocks/kibana_core'; +import { PersistHookProps } from '../types'; import { ReturnPersistExceptionItem, usePersistExceptionItem } from './persist_exception_item'; @@ -16,38 +18,66 @@ jest.mock('../api'); const mockKibanaHttpService = createKibanaCoreStartMock().http; describe('usePersistExceptionItem', () => { - test('init', async () => { - const onError = jest.fn(); - const { result } = renderHook(() => + const onError = jest.fn(); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('initializes hook', async () => { + const { result } = renderHook(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError }) ); expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); }); - test('saving exception item with isLoading === true', async () => { + test('"isLoading" is "true" when exception item is being saved', async () => { await act(async () => { - const onError = jest.fn(); - const { result, rerender, waitForNextUpdate } = renderHook( - () => usePersistExceptionItem({ http: mockKibanaHttpService, onError }) - ); + const { result, rerender, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionItem + >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); + await waitForNextUpdate(); result.current[1](getExceptionListItemSchemaMock()); rerender(); + expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]); }); }); - test('saved exception item with isSaved === true', async () => { - const onError = jest.fn(); + test('"isSaved" is "true" when exception item saved successfully', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - usePersistExceptionItem({ http: mockKibanaHttpService, onError }) - ); + const { result, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionItem + >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); + await waitForNextUpdate(); result.current[1](getExceptionListItemSchemaMock()); await waitForNextUpdate(); + expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); }); }); + + test('"onError" callback is invoked and "isSaved" is "false" when api call fails', async () => { + const error = new Error('persist rule failed'); + jest.spyOn(api, 'addExceptionListItem').mockRejectedValue(error); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionItem + >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); + + await waitForNextUpdate(); + result.current[1](getExceptionListItemSchemaMock()); + await waitForNextUpdate(); + + expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); + expect(onError).toHaveBeenCalledWith(error); + }); + }); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.tsx b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.tsx index 0ed007e805013..d9fe3a82ac177 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.tsx @@ -19,6 +19,13 @@ export type ReturnPersistExceptionItem = [ Dispatch ]; +/** + * Hook for creating or updating ExceptionListItem + * + * @param http Kibana http service + * @param onError error callback + * + */ export const usePersistExceptionItem = ({ http, onError, diff --git a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.tsx b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.tsx index 605dd635aa4f5..80d6e27043c99 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.tsx @@ -6,8 +6,10 @@ import { act, renderHook } from '@testing-library/react-hooks'; +import * as api from '../api'; import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock'; import { createKibanaCoreStartMock } from '../../common/mocks/kibana_core'; +import { PersistHookProps } from '../types'; import { ReturnPersistExceptionList, usePersistExceptionList } from './persist_exception_list'; @@ -16,38 +18,63 @@ jest.mock('../api'); const mockKibanaHttpService = createKibanaCoreStartMock().http; describe('usePersistExceptionList', () => { - test('init', async () => { - const onError = jest.fn(); - const { result } = renderHook(() => + const onError = jest.fn(); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('initializes hook', async () => { + const { result } = renderHook(() => usePersistExceptionList({ http: mockKibanaHttpService, onError }) ); expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); }); - test('saving exception list with isLoading === true', async () => { - const onError = jest.fn(); + test('"isLoading" is "true" when exception item is being saved', async () => { await act(async () => { - const { result, rerender, waitForNextUpdate } = renderHook( - () => usePersistExceptionList({ http: mockKibanaHttpService, onError }) - ); + const { result, rerender, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionList + >(() => usePersistExceptionList({ http: mockKibanaHttpService, onError })); await waitForNextUpdate(); result.current[1](getExceptionListSchemaMock()); rerender(); + expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]); }); }); - test('saved exception list with isSaved === true', async () => { - const onError = jest.fn(); + test('"isSaved" is "true" when exception item saved successfully', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - usePersistExceptionList({ http: mockKibanaHttpService, onError }) - ); + const { result, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionList + >(() => usePersistExceptionList({ http: mockKibanaHttpService, onError })); await waitForNextUpdate(); result.current[1](getExceptionListSchemaMock()); await waitForNextUpdate(); + expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); }); }); + + test('"onError" callback is invoked and "isSaved" is "false" when api call fails', async () => { + const error = new Error('persist rule failed'); + jest.spyOn(api, 'addExceptionList').mockRejectedValue(error); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + PersistHookProps, + ReturnPersistExceptionList + >(() => usePersistExceptionList({ http: mockKibanaHttpService, onError })); + await waitForNextUpdate(); + result.current[1](getExceptionListSchemaMock()); + await waitForNextUpdate(); + + expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); + expect(onError).toHaveBeenCalledWith(error); + }); + }); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.tsx b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.tsx index 45330c9725ae7..5848a17145194 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.tsx @@ -19,6 +19,13 @@ export type ReturnPersistExceptionList = [ Dispatch ]; +/** + * Hook for creating or updating ExceptionList + * + * @param http Kibana http service + * @param onError error callback + * + */ export const usePersistExceptionList = ({ http, onError, diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.test.tsx b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.test.tsx index 308d1cf4d1b17..a6a25ab4d4e9d 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.test.tsx @@ -8,6 +8,9 @@ import { act, renderHook } from '@testing-library/react-hooks'; import * as api from '../api'; import { createKibanaCoreStartMock } from '../../common/mocks/kibana_core'; +import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock'; +import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock'; +import { ExceptionListAndItems, UseExceptionListProps } from '../types'; import { ReturnExceptionListAndItems, useExceptionList } from './use_exception_list'; @@ -16,103 +19,166 @@ jest.mock('../api'); const mockKibanaHttpService = createKibanaCoreStartMock().http; describe('useExceptionList', () => { - test('init', async () => { - const onError = jest.fn(); + const onErrorMock = jest.fn(); + + afterEach(() => { + onErrorMock.mockClear(); + jest.clearAllMocks(); + }); + + test('initializes hook', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useExceptionList({ http: mockKibanaHttpService, id: 'myListId', onError }) + const { result, waitForNextUpdate } = renderHook< + UseExceptionListProps, + ReturnExceptionListAndItems + >(() => + useExceptionList({ + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }) ); await waitForNextUpdate(); - expect(result.current).toEqual([true, null]); + + expect(result.current).toEqual([true, null, result.current[2]]); + expect(typeof result.current[2]).toEqual('function'); }); }); test('fetch exception list and items', async () => { - const onError = jest.fn(); await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useExceptionList({ http: mockKibanaHttpService, id: 'myListId', onError }) + const { result, waitForNextUpdate } = renderHook< + UseExceptionListProps, + ReturnExceptionListAndItems + >(() => + useExceptionList({ + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }) ); await waitForNextUpdate(); await waitForNextUpdate(); - expect(result.current).toEqual([ - false, - { - _tags: ['endpoint', 'process', 'malware', 'os:linux'], - created_at: '2020-04-23T00:19:13.289Z', - created_by: 'user_name', - description: 'This is a sample endpoint type exception', - exceptionItems: { - data: [ - { - _tags: ['endpoint', 'process', 'malware', 'os:linux'], - comment: [], - created_at: '2020-04-23T00:19:13.289Z', - created_by: 'user_name', - description: 'This is a sample endpoint type exception', - entries: [ - { - field: 'actingProcess.file.signer', - match: 'Elastic, N.V.', - match_any: undefined, - operator: 'included', - }, - { - field: 'event.category', - match: undefined, - match_any: ['process', 'malware'], - operator: 'included', - }, - ], - id: '1', - item_id: 'endpoint_list_item', - list_id: 'endpoint_list', - meta: {}, - name: 'Sample Endpoint Exception List', - namespace_type: 'single', - tags: ['user added string for a tag', 'malware'], - tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f', - type: 'simple', - updated_at: '2020-04-23T00:19:13.289Z', - updated_by: 'user_name', - }, - ], + + const expectedResult: ExceptionListAndItems = { + ...getExceptionListSchemaMock(), + exceptionItems: { + items: [{ ...getExceptionListItemSchemaMock() }], + pagination: { page: 1, - per_page: 20, + perPage: 20, total: 1, }, - id: '1', - list_id: 'endpoint_list', - meta: {}, - name: 'Sample Endpoint Exception List', - namespace_type: 'single', - tags: ['user added string for a tag', 'malware'], - tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f', - type: 'endpoint', - updated_at: '2020-04-23T00:19:13.289Z', - updated_by: 'user_name', }, - ]); + }; + + expect(result.current).toEqual([false, expectedResult, result.current[2]]); }); }); test('fetch a new exception list and its items', async () => { - const onError = jest.fn(); const spyOnfetchExceptionListById = jest.spyOn(api, 'fetchExceptionListById'); const spyOnfetchExceptionListItemsByListId = jest.spyOn(api, 'fetchExceptionListItemsByListId'); await act(async () => { - const { rerender, waitForNextUpdate } = renderHook( - (id) => useExceptionList({ http: mockKibanaHttpService, id, onError }), + const { rerender, waitForNextUpdate } = renderHook< + UseExceptionListProps, + ReturnExceptionListAndItems + >( + ({ filterOptions, http, id, namespaceType, pagination, onError }) => + useExceptionList({ filterOptions, http, id, namespaceType, onError, pagination }), { - initialProps: 'myListId', + initialProps: { + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }, } ); await waitForNextUpdate(); + rerender({ + http: mockKibanaHttpService, + id: 'newListId', + namespaceType: 'single', + onError: onErrorMock, + }); + await waitForNextUpdate(); + + expect(spyOnfetchExceptionListById).toHaveBeenCalledTimes(2); + expect(spyOnfetchExceptionListItemsByListId).toHaveBeenCalledTimes(2); + }); + }); + + test('fetches list and items when refreshExceptionList callback invoked', async () => { + const spyOnfetchExceptionListById = jest.spyOn(api, 'fetchExceptionListById'); + const spyOnfetchExceptionListItemsByListId = jest.spyOn(api, 'fetchExceptionListItemsByListId'); + await act(async () => { + const { result, waitForNextUpdate } = renderHook< + UseExceptionListProps, + ReturnExceptionListAndItems + >(() => + useExceptionList({ + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }) + ); + await waitForNextUpdate(); await waitForNextUpdate(); - rerender('newListId'); + result.current[2](); await waitForNextUpdate(); + expect(spyOnfetchExceptionListById).toHaveBeenCalledTimes(2); expect(spyOnfetchExceptionListItemsByListId).toHaveBeenCalledTimes(2); }); }); + + test('invokes "onError" callback if "fetchExceptionListItemsByListId" fails', async () => { + const mockError = new Error('failed to fetch list items'); + const spyOnfetchExceptionListById = jest.spyOn(api, 'fetchExceptionListById'); + const spyOnfetchExceptionListItemsByListId = jest + .spyOn(api, 'fetchExceptionListItemsByListId') + .mockRejectedValue(mockError); + await act(async () => { + const { waitForNextUpdate } = renderHook( + () => + useExceptionList({ + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(spyOnfetchExceptionListById).toHaveBeenCalledTimes(1); + expect(onErrorMock).toHaveBeenCalledWith(mockError); + expect(spyOnfetchExceptionListItemsByListId).toHaveBeenCalledTimes(1); + }); + }); + + test('invokes "onError" callback if "fetchExceptionListById" fails', async () => { + const mockError = new Error('failed to fetch list'); + jest.spyOn(api, 'fetchExceptionListById').mockRejectedValue(mockError); + + await act(async () => { + const { waitForNextUpdate } = renderHook( + () => + useExceptionList({ + http: mockKibanaHttpService, + id: 'myListId', + namespaceType: 'single', + onError: onErrorMock, + }) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(onErrorMock).toHaveBeenCalledWith(mockError); + }); + }); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.tsx b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.tsx index d0ac357e05aa0..116233cd89348 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.tsx +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list.tsx @@ -4,66 +4,124 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { fetchExceptionListById, fetchExceptionListItemsByListId } from '../api'; import { ExceptionListAndItems, UseExceptionListProps } from '../types'; -export type ReturnExceptionListAndItems = [boolean, ExceptionListAndItems | null]; +export type ReturnExceptionListAndItems = [boolean, ExceptionListAndItems | null, () => void]; /** * Hook for using to get an ExceptionList and it's ExceptionListItems * + * @param http Kibana http service * @param id desired ExceptionList ID (not list_id) + * @param namespaceType list namespaceType determines list space + * @param onError error callback + * @param filterOptions optional - filter by fields or tags + * @param pagination optional * */ export const useExceptionList = ({ http, id, + namespaceType, + pagination = { + page: 1, + perPage: 20, + total: 0, + }, + filterOptions = { + filter: '', + tags: [], + }, onError, }: UseExceptionListProps): ReturnExceptionListAndItems => { const [exceptionListAndItems, setExceptionList] = useState(null); + const [shouldRefresh, setRefresh] = useState(true); + const refreshExceptionList = useCallback(() => setRefresh(true), [setRefresh]); const [loading, setLoading] = useState(true); + const tags = filterOptions.tags.sort().join(); - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); + useEffect( + () => { + let isSubscribed = true; + const abortCtrl = new AbortController(); - const fetchData = async (idToFetch: string): Promise => { - try { - setLoading(true); - const exceptionList = await fetchExceptionListById({ - http, - id: idToFetch, - signal: abortCtrl.signal, - }); - const exceptionListItems = await fetchExceptionListItemsByListId({ - http, - listId: exceptionList.list_id, - signal: abortCtrl.signal, - }); - if (isSubscribed) { - setExceptionList({ ...exceptionList, exceptionItems: { ...exceptionListItems } }); + const fetchData = async (idToFetch: string): Promise => { + if (shouldRefresh) { + try { + setLoading(true); + + const { + list_id, + namespace_type, + ...restOfExceptionList + } = await fetchExceptionListById({ + http, + id: idToFetch, + namespaceType, + signal: abortCtrl.signal, + }); + const fetchListItemsResult = await fetchExceptionListItemsByListId({ + filterOptions, + http, + listId: list_id, + namespaceType: namespace_type, + pagination, + signal: abortCtrl.signal, + }); + + setRefresh(false); + + if (isSubscribed) { + setExceptionList({ + list_id, + namespace_type, + ...restOfExceptionList, + exceptionItems: { + items: [...fetchListItemsResult.data], + pagination: { + page: fetchListItemsResult.page, + perPage: fetchListItemsResult.per_page, + total: fetchListItemsResult.total, + }, + }, + }); + } + } catch (error) { + setRefresh(false); + if (isSubscribed) { + setExceptionList(null); + onError(error); + } + } } - } catch (error) { + if (isSubscribed) { - setExceptionList(null); - onError(error); + setLoading(false); } - } - if (isSubscribed) { - setLoading(false); - } - }; + }; - if (id != null) { - fetchData(id); - } - return (): void => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [http, id, onError]); + if (id != null) { + fetchData(id); + } + return (): void => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [ + http, + id, + onError, + shouldRefresh, + pagination.page, + pagination.perPage, + filterOptions.filter, + tags, + ] + ); - return [loading, exceptionListAndItems]; + return [loading, exceptionListAndItems, refreshExceptionList]; }; diff --git a/x-pack/plugins/lists/public/exceptions/types.ts b/x-pack/plugins/lists/public/exceptions/types.ts index fcf2108e7323a..cf6b6c3ec1c59 100644 --- a/x-pack/plugins/lists/public/exceptions/types.ts +++ b/x-pack/plugins/lists/public/exceptions/types.ts @@ -9,12 +9,28 @@ import { CreateExceptionListSchemaPartial, ExceptionListItemSchema, ExceptionListSchema, - FoundExceptionListItemSchema, + NamespaceType, } from '../../common/schemas'; import { HttpStart } from '../../../../../src/core/public'; +export interface FilterExceptionsOptions { + filter: string; + tags: string[]; +} + +export interface Pagination { + page: number; + perPage: number; + total: number; +} + +export interface ExceptionItemsAndPagination { + items: ExceptionListItemSchema[]; + pagination: Pagination; +} + export interface ExceptionListAndItems extends ExceptionListSchema { - exceptionItems: FoundExceptionListItemSchema; + exceptionItems: ExceptionItemsAndPagination; } export type AddExceptionList = ExceptionListSchema | CreateExceptionListSchemaPartial; @@ -27,20 +43,27 @@ export interface PersistHookProps { } export interface UseExceptionListProps { + filterOptions?: FilterExceptionsOptions; http: HttpStart; id: string | undefined; + namespaceType: NamespaceType; onError: (arg: Error) => void; + pagination?: Pagination; } export interface ApiCallByListIdProps { http: HttpStart; listId: string; + namespaceType: NamespaceType; + filterOptions?: FilterExceptionsOptions; + pagination?: Pagination; signal: AbortSignal; } export interface ApiCallByIdProps { http: HttpStart; id: string; + namespaceType: NamespaceType; signal: AbortSignal; } diff --git a/x-pack/plugins/logstash/public/application/index.tsx b/x-pack/plugins/logstash/public/application/index.tsx index 3588e1f6b2417..8d515ad6b3932 100644 --- a/x-pack/plugins/logstash/public/application/index.tsx +++ b/x-pack/plugins/logstash/public/application/index.tsx @@ -6,7 +6,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'; +import { Router, Route, Switch, Redirect } from 'react-router-dom'; import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; @@ -30,7 +30,7 @@ import * as Breadcrumbs from './breadcrumbs'; export const renderApp = async ( core: CoreStart, - { basePath, element, setBreadcrumbs }: ManagementAppMountParams, + { history, element, setBreadcrumbs }: ManagementAppMountParams, isMonitoringEnabled: boolean, licenseService$: Observable ) => { @@ -43,12 +43,12 @@ export const renderApp = async ( ReactDOM.render( - + { + render={() => { setBreadcrumbs(Breadcrumbs.getPipelineListBreadcrumbs()); return ( history.push(`/pipeline/${id}/edit`)} clonePipeline={(id: string) => history.push(`/pipeline/${id}/edit?clone`)} - createPipeline={() => history.push(`/pipeline/new-pipeline`)} + createPipeline={() => history.push(`pipeline/new-pipeline`)} pipelinesService={pipelinesService} toastNotifications={core.notifications.toasts} /> @@ -70,7 +70,7 @@ export const renderApp = async ( ( + render={() => ( ( + render={({ match }) => ( - + , element ); diff --git a/x-pack/plugins/logstash/public/plugin.ts b/x-pack/plugins/logstash/public/plugin.ts index 70fdb420ca2d2..ade6abdb63f43 100644 --- a/x-pack/plugins/logstash/public/plugin.ts +++ b/x-pack/plugins/logstash/public/plugin.ts @@ -71,7 +71,7 @@ export class LogstashPlugin implements Plugin { defaultMessage: 'Create, delete, update, and clone data ingestion pipelines.', }), icon: 'pipelineApp', - path: '/app/kibana#/management/ingest/pipelines', + path: '/app/management/ingest/pipelines', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 8fa44c512df4b..d357f11f5e3e1 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -30,6 +30,7 @@ export const TELEMETRY_TYPE = 'maps-telemetry'; export const MAP_APP_PATH = `app/${APP_ID}`; export const GIS_API_PATH = `api/${APP_ID}`; export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`; +export const FONTS_API_PATH = `${GIS_API_PATH}/fonts`; export const MAP_BASE_URL = `/${MAP_APP_PATH}#/${MAP_SAVED_OBJECT_TYPE}`; diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts index 4f61d7501f977..c7bfe94742bd6 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts @@ -7,7 +7,7 @@ import { RENDER_AS, SORT_ORDER, SCALING_TYPES } from '../constants'; import { MapExtent, MapQuery } from './map_descriptor'; -import { Filter, TimeRange } from '../../../../../src/plugins/data/public'; +import { Filter, TimeRange } from '../../../../../src/plugins/data/common'; // Global map state passed to every layer. export type MapFilters = { diff --git a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts index 798b5f335dda2..00380ca12a486 100644 --- a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts +++ b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts @@ -5,7 +5,7 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import { Query } from '../../../../../src/plugins/data/public'; +import { Query } from '../../../../../src/plugins/data/common'; import { DRAW_TYPE, ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../constants'; export type MapExtent = { diff --git a/x-pack/plugins/maps/public/components/no_index_pattern_callout.js b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js index 89cd884a6dd32..2daab8c6322dd 100644 --- a/x-pack/plugins/maps/public/components/no_index_pattern_callout.js +++ b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js @@ -24,7 +24,7 @@ export function NoIndexPatternCallout() { id="xpack.maps.noIndexPattern.doThisPrefixDescription" defaultMessage="You'll need to " /> - + { require('./kibana_services').getEmsLandingPageUrl = () => 'http://test.com'; }); - it('should construct EMSClient with absolute file and tile API urls', async () => { + test('should construct EMSClient with absolute file and tile API urls', async () => { getEMSClient(); const mockEmsClientCall = EMSClient.mock.calls[0]; expect(mockEmsClientCall[0].fileApiUrl.startsWith('https://file-api')).toBe(true); expect(mockEmsClientCall[0].tileApiUrl.startsWith('https://tile-api')).toBe(true); }); }); + +describe('getGlyphUrl', () => { + describe('EMS enabled', () => { + const EMS_FONTS_URL_MOCK = 'ems/fonts'; + beforeAll(() => { + require('./kibana_services').getIsEmsEnabled = () => true; + require('./kibana_services').getEmsFontLibraryUrl = () => EMS_FONTS_URL_MOCK; + }); + + describe('EMS proxy enabled', () => { + beforeAll(() => { + require('./kibana_services').getProxyElasticMapsServiceInMaps = () => true; + }); + + test('should return proxied EMS fonts URL', async () => { + expect(getGlyphUrl()).toBe('http://localhost/api/maps/ems/tiles/fonts/{fontstack}/{range}'); + }); + }); + + describe('EMS proxy disabled', () => { + beforeAll(() => { + require('./kibana_services').getProxyElasticMapsServiceInMaps = () => false; + }); + + test('should return EMS fonts URL', async () => { + expect(getGlyphUrl()).toBe(EMS_FONTS_URL_MOCK); + }); + }); + }); + + describe('EMS disabled', () => { + beforeAll(() => { + const mockHttp = { + basePath: { + prepend: (path) => `abc${path}`, + }, + }; + require('./kibana_services').getHttp = () => mockHttp; + require('./kibana_services').getIsEmsEnabled = () => false; + }); + + test('should return kibana fonts URL', async () => { + expect(getGlyphUrl()).toBe('abc/api/maps/fonts/{fontstack}/{range}'); + }); + }); +}); diff --git a/x-pack/plugins/maps/server/fonts/open_sans/0-255.pbf b/x-pack/plugins/maps/server/fonts/open_sans/0-255.pbf new file mode 100644 index 0000000000000..ab811ae10a2e7 Binary files /dev/null and b/x-pack/plugins/maps/server/fonts/open_sans/0-255.pbf differ diff --git a/x-pack/plugins/maps/server/fonts/open_sans/1024-1279.pbf b/x-pack/plugins/maps/server/fonts/open_sans/1024-1279.pbf new file mode 100644 index 0000000000000..7cda8da1d0388 Binary files /dev/null and b/x-pack/plugins/maps/server/fonts/open_sans/1024-1279.pbf differ diff --git a/x-pack/plugins/maps/server/fonts/open_sans/256-511.pbf b/x-pack/plugins/maps/server/fonts/open_sans/256-511.pbf new file mode 100644 index 0000000000000..6e108e53a26f8 Binary files /dev/null and b/x-pack/plugins/maps/server/fonts/open_sans/256-511.pbf differ diff --git a/x-pack/plugins/maps/server/fonts/open_sans/768-1023.pbf b/x-pack/plugins/maps/server/fonts/open_sans/768-1023.pbf new file mode 100644 index 0000000000000..a3efbb9361d4d Binary files /dev/null and b/x-pack/plugins/maps/server/fonts/open_sans/768-1023.pbf differ diff --git a/x-pack/plugins/maps/server/fonts/open_sans/8192-8447.pbf b/x-pack/plugins/maps/server/fonts/open_sans/8192-8447.pbf new file mode 100644 index 0000000000000..e053cb51c438c Binary files /dev/null and b/x-pack/plugins/maps/server/fonts/open_sans/8192-8447.pbf differ diff --git a/x-pack/plugins/maps/server/fonts/open_sans/license.txt b/x-pack/plugins/maps/server/fonts/open_sans/license.txt new file mode 100644 index 0000000000000..7783de532a331 --- /dev/null +++ b/x-pack/plugins/maps/server/fonts/open_sans/license.txt @@ -0,0 +1,53 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/x-pack/plugins/maps/server/routes.js b/x-pack/plugins/maps/server/routes.js index 63895ea8b9822..ad66712eb3ad6 100644 --- a/x-pack/plugins/maps/server/routes.js +++ b/x-pack/plugins/maps/server/routes.js @@ -21,12 +21,15 @@ import { GIS_API_PATH, EMS_SPRITES_PATH, INDEX_SETTINGS_API_PATH, + FONTS_API_PATH, } from '../common/constants'; import { EMSClient } from '@elastic/ems-client'; import fetch from 'node-fetch'; import { i18n } from '@kbn/i18n'; import { getIndexPatternSettings } from './lib/get_index_pattern_settings'; import { schema } from '@kbn/config-schema'; +import fs from 'fs'; +import path from 'path'; const ROOT = `/${GIS_API_PATH}`; @@ -76,7 +79,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -108,7 +111,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_TILES_RASTER_TILE_PATH}`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -144,7 +147,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_CATALOGUE_PATH}`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -180,7 +183,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_FILES_CATALOGUE_PATH}/{emsVersion}/manifest`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -210,7 +213,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_TILES_CATALOGUE_PATH}/{emsVersion}/manifest`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -258,7 +261,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -294,7 +297,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -344,7 +347,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -386,7 +389,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -423,7 +426,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { path: `${ROOT}/${EMS_TILES_API_PATH}/${EMS_GLYPHS_PATH}/{fontstack}/{range}`, validate: false, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -444,7 +447,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, { ok, badRequest }) => { + async (context, request, { ok, badRequest }) => { if (!checkEMSProxyEnabled()) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } @@ -481,6 +484,39 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } ); + router.get( + { + path: `/${FONTS_API_PATH}/{fontstack}/{range}`, + validate: { + params: schema.object({ + fontstack: schema.string(), + range: schema.string(), + }), + }, + }, + (context, request, response) => { + return new Promise((resolve, reject) => { + const santizedRange = path.normalize(request.params.range); + const fontPath = path.join(__dirname, 'fonts', 'open_sans', `${santizedRange}.pbf`); + fs.readFile(fontPath, (error, data) => { + if (error) { + reject( + response.custom({ + statusCode: 404, + }) + ); + } else { + resolve( + response.ok({ + body: data, + }) + ); + } + }); + }); + } + ); + router.get( { path: `/${INDEX_SETTINGS_API_PATH}`, @@ -490,7 +526,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { }), }, }, - async (con, request, response) => { + async (context, request, response) => { const { query } = request; if (!query.indexPatternTitle) { @@ -502,7 +538,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } try { - const resp = await con.core.elasticsearch.legacy.client.callAsCurrentUser( + const resp = await context.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.getSettings', { index: query.indexPatternTitle, diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts index bc55c7549c589..c75387a4b410b 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts @@ -50,6 +50,7 @@ export interface AnalysisConfig { latency?: number; multivariate_by_fields?: boolean; summary_count_field_name?: string; + per_partition_categorization?: PerPartitionCategorization; } export interface Detector { @@ -86,3 +87,8 @@ export interface CustomRule { scope?: object; conditions: any[]; } + +export interface PerPartitionCategorization { + enabled: boolean; + stop_on_warn?: boolean; +} diff --git a/x-pack/plugins/ml/common/types/categories.ts b/x-pack/plugins/ml/common/types/categories.ts index 5d4c3eab53ee8..b3655f274b362 100644 --- a/x-pack/plugins/ml/common/types/categories.ts +++ b/x-pack/plugins/ml/common/types/categories.ts @@ -16,6 +16,8 @@ export interface Category { max_matching_length: number; examples: string[]; grok_pattern: string; + partition_field_name?: string; // TODO: make non-optional once fields have been added to the results + partition_field_value?: string; // TODO: make non-optional once fields have been added to the results } export interface Token { diff --git a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx index 9cea47ded09b4..fd2b7902833a6 100644 --- a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx +++ b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx @@ -6,7 +6,7 @@ import React, { FC } from 'react'; -import { EuiSpacer, EuiInMemoryTable } from '@elastic/eui'; +import { EuiSpacer, EuiInMemoryTable, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; // @ts-ignore import { formatDate } from '@elastic/eui/lib/services/format'; import { i18n } from '@kbn/i18n'; @@ -21,16 +21,33 @@ interface JobMessagesProps { messages: JobMessage[]; loading: boolean; error: string; + refreshMessage?: React.MouseEventHandler; } /** * Component for rendering job messages for anomaly detection * and data frame analytics jobs. */ -export const JobMessages: FC = ({ messages, loading, error }) => { +export const JobMessages: FC = ({ messages, loading, error, refreshMessage }) => { const columns = [ { - name: '', + name: refreshMessage ? ( + + + + ) : ( + '' + ), render: (message: JobMessage) => , width: `${theme.euiSizeL}`, }, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx index fc860251bf83d..0dd9eba172e1c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_messages_pane.tsx @@ -22,7 +22,6 @@ export const ExpandedRowMessagesPane: FC = ({ analyticsId }) => { const getMessagesFactory = () => { let concurrentLoads = 0; - return async function getMessages() { try { concurrentLoads++; @@ -52,8 +51,14 @@ export const ExpandedRowMessagesPane: FC = ({ analyticsId }) => { } }; }; - useRefreshAnalyticsList({ onRefresh: getMessagesFactory() }); - return ; + return ( + + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx index 126fd25a536f6..fd86d9f48f46d 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx @@ -182,10 +182,7 @@ export const DatavisualizerSelector: FC = () => { } description={startTrialDescription()} footer={ - + = ({ /> } description="" - href={`${basePath.get()}/app/kibana#/management/data/index_management/indices/filter/${index}`} + href={`${basePath.get()}/app/management/data/index_management/indices/filter/${index}`} /> @@ -153,7 +153,7 @@ export const ResultsLinks: FC = ({ /> } description="" - href={`${basePath.get()}/app/kibana#/management/kibana/indexPatterns${ + href={`${basePath.get()}/app/management/kibana/indexPatterns${ createIndexPattern ? `/patterns/${indexPatternId}` : '' }`} /> diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js index 67de83e90695d..eeff91be130ea 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js @@ -167,7 +167,7 @@ class CreateWatchService { saveWatch(watchModel) .then(() => { this.status.watch = this.STATUS.SAVED; - this.config.watcherEditURL = `${basePath.get()}/app/kibana#/management/insightsAndAlerting/watcher/watches/watch/${id}/edit?_g=()`; + this.config.watcherEditURL = `${basePath.get()}/app/management/insightsAndAlerting/watcher/watches/watch/${id}/edit?_g=()`; resolve({ id, url: this.config.watcherEditURL, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js index 50e5aeeb29dd9..8f89c4a049189 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js @@ -133,7 +133,7 @@ export function extractJobDetails(job) { defaultMessage: 'Datafeed', }), position: 'left', - items: filterObjects(job.datafeed_config, true, true), + items: filterObjects(job.datafeed_config || {}, true, true), }; if (job.node) { datafeed.items.push(['node', JSON.stringify(job.node)]); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js index 0375997b86bb8..56da4f1e0ff84 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js @@ -125,7 +125,7 @@ export class JobDetails extends Component { }, ]; - if (showFullDetails) { + if (showFullDetails && datafeed.items.length) { // Datafeed should be at index 2 in tabs array for full details tabs.splice(2, 0, { id: 'datafeed', diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx index fbb64db94cd56..486de90d2299c 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect, useState } from 'react'; - +import React, { FC, useCallback, useEffect, useState } from 'react'; import { ml } from '../../../../services/ml_api_service'; import { JobMessages } from '../../../../components/job_messages'; import { JobMessage } from '../../../../../../common/types/audit_message'; - interface JobMessagesPaneProps { jobId: string; } @@ -32,9 +30,18 @@ export const JobMessagesPane: FC = ({ jobId }) => { } }; + const refreshMessage = useCallback(fetchMessages, [jobId]); + useEffect(() => { fetchMessages(); }, []); - return ; + return ( + + ); }; diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/index.ts b/x-pack/plugins/ml/public/application/management/jobs_list/index.ts index cfe37ce14bb78..5d1fc6f0a3c92 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/index.ts +++ b/x-pack/plugins/ml/public/application/management/jobs_list/index.ts @@ -11,12 +11,14 @@ import { ManagementAppMountParams } from '../../../../../../../src/plugins/manag import { MlStartDependencies } from '../../../plugin'; import { JobsListPage } from './components'; import { getJobsListBreadcrumbs } from '../breadcrumbs'; +import { setDependencyCache, clearCache } from '../../util/dependency_cache'; const renderApp = (element: HTMLElement, coreStart: CoreStart) => { const I18nContext = coreStart.i18n.Context; ReactDOM.render(React.createElement(JobsListPage, { I18nContext }), element); return () => { unmountComponentAtNode(element); + clearCache(); }; }; @@ -25,6 +27,15 @@ export async function mountApp( params: ManagementAppMountParams ) { const [coreStart] = await core.getStartServices(); + + setDependencyCache({ + docLinks: coreStart.docLinks!, + basePath: coreStart.http.basePath, + http: coreStart.http, + i18n: coreStart.i18n, + }); + params.setBreadcrumbs(getJobsListBreadcrumbs()); + return renderApp(params.element, coreStart); } diff --git a/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx b/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx index 87a7156b6f52e..119346ec8035a 100644 --- a/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx @@ -42,7 +42,7 @@ export const OverviewSideBar: FC = ({ createAnomalyDetectionJobDisabled } const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; const docsLink = `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/xpack-ml.html`; - const transformsLink = `${basePath.get()}/app/kibana#/management/data/transform`; + const transformsLink = `${basePath.get()}/app/management/data/transform`; return ( diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index 88b86de322e3c..de393e002c55b 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -77,6 +77,12 @@ export const analysisConfigSchema = schema.object({ detectors: schema.arrayOf(detectorSchema), influencers: schema.arrayOf(schema.maybe(schema.string())), categorization_field_name: schema.maybe(schema.string()), + per_partition_categorization: schema.maybe( + schema.object({ + enabled: schema.boolean(), + stop_on_warn: schema.maybe(schema.boolean()), + }) + ), }); export const anomalyDetectionJobSchema = { diff --git a/x-pack/plugins/monitoring/kibana.json b/x-pack/plugins/monitoring/kibana.json index 115cc08871ea4..4ed693464712d 100644 --- a/x-pack/plugins/monitoring/kibana.json +++ b/x-pack/plugins/monitoring/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "configPath": ["monitoring"], "requiredPlugins": ["licensing", "features", "data", "navigation", "kibanaLegacy"], - "optionalPlugins": ["alerting", "actions", "infra", "telemetryCollectionManager", "usageCollection", "home"], + "optionalPlugins": ["alerts", "actions", "infra", "telemetryCollectionManager", "usageCollection", "home"], "server": true, "ui": true } diff --git a/x-pack/plugins/monitoring/public/components/alerts/status.tsx b/x-pack/plugins/monitoring/public/components/alerts/status.tsx index cdddbf1031303..6f72168f5069b 100644 --- a/x-pack/plugins/monitoring/public/components/alerts/status.tsx +++ b/x-pack/plugins/monitoring/public/components/alerts/status.tsx @@ -18,7 +18,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Legacy } from '../../legacy_shims'; -import { Alert, BASE_ALERT_API_PATH } from '../../../../../plugins/alerting/common'; +import { Alert, BASE_ALERT_API_PATH } from '../../../../alerts/common'; import { getSetupModeState, addSetupModeCallback, toggleSetupMode } from '../../lib/setup_mode'; import { NUMBER_OF_MIGRATED_ALERTS, ALERT_TYPE_PREFIX } from '../../../common/constants'; import { AlertsConfiguration } from './configuration'; diff --git a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js index 263da16340cda..f1a867536b606 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js @@ -288,7 +288,7 @@ const handleClickIncompatibleLicense = (scope, clusterName) => { }; const handleClickInvalidLicense = (scope, clusterName) => { - const licensingPath = `${Legacy.shims.getBasePath()}/app/kibana#/management/stack/license_management/home`; + const licensingPath = `${Legacy.shims.getBasePath()}/app/management/stack/license_management/home`; licenseWarning(scope, { title: toMountPoint( diff --git a/x-pack/plugins/monitoring/public/components/license/index.js b/x-pack/plugins/monitoring/public/components/license/index.js index e8ea1f8df227a..076b8e6d543e6 100644 --- a/x-pack/plugins/monitoring/public/components/license/index.js +++ b/x-pack/plugins/monitoring/public/components/license/index.js @@ -169,7 +169,7 @@ const LicenseUpdateInfoForRemote = ({ isPrimaryCluster }) => { export function License(props) { const { status, type, isExpired, expiryDate } = props; - const licenseManagement = `${Legacy.shims.getBasePath()}/app/kibana#/management/stack/license_management`; + const licenseManagement = `${Legacy.shims.getBasePath()}/app/management/stack/license_management`; return ( diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts index bcc1a8abe5cb0..6262036037712 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts @@ -10,7 +10,7 @@ import { AlertCommonParams, AlertCommonState, AlertClusterStatePerClusterState } import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; import { executeActions } from '../lib/alerts/cluster_state.lib'; import { AlertClusterStateState } from './enums'; -import { alertsMock, AlertServicesMock } from '../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../alerts/server/mocks'; jest.mock('../lib/alerts/cluster_state.lib', () => ({ executeActions: jest.fn(), diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts index 6567d1c6def31..5b6521179002a 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts @@ -8,7 +8,7 @@ import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; -import { AlertType } from '../../../alerting/server'; +import { AlertType } from '../../../alerts/server'; import { executeActions, getUiMessage } from '../lib/alerts/cluster_state.lib'; import { AlertCommonExecutorOptions, diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts index 6ffe937679f4c..fb8d10884fdc7 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts @@ -16,7 +16,7 @@ import { } from './types'; import { executeActions } from '../lib/alerts/license_expiration.lib'; import { PreparedAlert, getPreparedAlert } from '../lib/alerts/get_prepared_alert'; -import { alertsMock, AlertServicesMock } from '../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../alerts/server/mocks'; jest.mock('../lib/alerts/license_expiration.lib', () => ({ executeActions: jest.fn(), diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts index 00402bca57a7e..d57f1a7655b18 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts @@ -8,7 +8,7 @@ import moment from 'moment-timezone'; import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; -import { AlertType } from '../../../../plugins/alerting/server'; +import { AlertType } from '../../../alerts/server'; import { fetchLicenses } from '../lib/alerts/fetch_licenses'; import { AlertCommonState, diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index b689d008b51a7..67c74635b4e36 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { Moment } from 'moment'; -import { AlertExecutorOptions } from '../../../alerting/server'; +import { AlertExecutorOptions } from '../../../alerts/server'; import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from './enums'; export interface AlertLicense { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts index ae66d603507ca..c4553d87980da 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { AlertInstance } from '../../../../alerting/server'; +import { AlertInstance } from '../../../../alerts/server'; import { AlertCommonCluster, AlertCommonPerClusterMessage, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts index 7a6c38865ebe8..614658baf5c79 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -6,7 +6,7 @@ import moment from 'moment'; import { Logger } from '../../../../../../src/core/server'; import { AlertCommonPerClusterState } from '../../alerts/types'; -import { AlertsClient } from '../../../../alerting/server'; +import { AlertsClient } from '../../../../alerts/server'; export async function fetchStatus( alertsClient: AlertsClient, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts index 83a9e26e4c589..cfaaeb36535a0 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts @@ -6,7 +6,7 @@ import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'kibana/server'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { AlertServices } from '../../../../alerting/server'; +import { AlertServices } from '../../../../alerts/server'; import { AlertCommonCluster } from '../../alerts/types'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../common/constants'; import { fetchAvailableCcs } from './fetch_available_ccs'; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts index cfe9f02b9bd6a..97ef2790b516d 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts @@ -5,7 +5,7 @@ */ import { Moment } from 'moment-timezone'; import { i18n } from '@kbn/i18n'; -import { AlertInstance } from '../../../../alerting/server'; +import { AlertInstance } from '../../../../alerts/server'; import { AlertCommonPerClusterMessageLinkToken, AlertCommonPerClusterMessageTimeToken, diff --git a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js index fcb54e92f649c..607503673276b 100644 --- a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js +++ b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js @@ -18,7 +18,7 @@ import { getLivesNodes } from '../../elasticsearch/nodes/get_nodes/get_live_node const NUMBER_OF_SECONDS_AGO_TO_LOOK = 30; -const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nodeUuid) => { +const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nodeUuid, size) => { const start = get(req.payload, 'timeRange.min') || `now-${NUMBER_OF_SECONDS_AGO_TO_LOOK}s`; const end = get(req.payload, 'timeRange.max') || 'now'; @@ -73,6 +73,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod es_uuids: { terms: { field: 'node_stats.node_id', + size, }, aggs: { by_timestamp: { @@ -85,6 +86,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod kibana_uuids: { terms: { field: 'kibana_stats.kibana.uuid', + size, }, aggs: { by_timestamp: { @@ -97,6 +99,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod beats_uuids: { terms: { field: 'beats_stats.beat.uuid', + size, }, aggs: { by_timestamp: { @@ -107,11 +110,13 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod beat_type: { terms: { field: 'beats_stats.beat.type', + size, }, }, cluster_uuid: { terms: { field: 'cluster_uuid', + size, }, }, }, @@ -119,6 +124,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod logstash_uuids: { terms: { field: 'logstash_stats.logstash.uuid', + size, }, aggs: { by_timestamp: { @@ -129,6 +135,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod cluster_uuid: { terms: { field: 'cluster_uuid', + size, }, }, }, @@ -348,6 +355,7 @@ export const getCollectionStatus = async ( ) => { const config = req.server.config(); const kibanaUuid = config.get('server.uuid'); + const size = config.get('monitoring.ui.max_bucket_size'); const hasPermissions = await hasNecessaryPermissions(req); if (!hasPermissions) { @@ -369,7 +377,7 @@ export const getCollectionStatus = async ( ]; const [recentDocuments, detectedProducts] = await Promise.all([ - await getRecentMonitoringDocuments(req, indexPatterns, clusterUuid, nodeUuid), + await getRecentMonitoringDocuments(req, indexPatterns, clusterUuid, nodeUuid, size), await detectProducts(req, isLiveCluster), ]); diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 14bef307b2f85..f4f38f70b1ccb 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -47,7 +47,7 @@ import { MonitoringLicenseService } from './types'; import { PluginStartContract as AlertingPluginStartContract, PluginSetupContract as AlertingPluginSetupContract, -} from '../../alerting/server'; +} from '../../alerts/server'; import { getLicenseExpiration } from './alerts/license_expiration'; import { getClusterState } from './alerts/cluster_state'; import { InfraPluginSetup } from '../../infra/server'; @@ -61,12 +61,12 @@ interface PluginsSetup { usageCollection?: UsageCollectionSetup; licensing: LicensingPluginSetup; features: FeaturesPluginSetupContract; - alerting: AlertingPluginSetupContract; + alerts: AlertingPluginSetupContract; infra: InfraPluginSetup; } interface PluginsStart { - alerting: AlertingPluginStartContract; + alerts: AlertingPluginStartContract; } interface MonitoringCoreConfig { @@ -156,7 +156,7 @@ export class Plugin { await this.licenseService.refresh(); if (KIBANA_ALERTING_ENABLED) { - plugins.alerting.registerType( + plugins.alerts.registerType( getLicenseExpiration( async () => { const coreStart = (await core.getStartServices())[0]; @@ -167,7 +167,7 @@ export class Plugin { config.ui.ccs.enabled ) ); - plugins.alerting.registerType( + plugins.alerts.registerType( getClusterState( async () => { const coreStart = (await core.getStartServices())[0]; @@ -357,7 +357,7 @@ export class Plugin { payload: req.body, getKibanaStatsCollector: () => this.legacyShimDependencies.kibanaStatsCollector, getUiSettingsService: () => context.core.uiSettings.client, - getAlertsClient: () => plugins.alerting.getAlertsClientWithRequest(req), + getAlertsClient: () => plugins.alerts.getAlertsClientWithRequest(req), server: { config: legacyConfigWrapper, newPlatform: { diff --git a/x-pack/plugins/remote_clusters/public/application/app.js b/x-pack/plugins/remote_clusters/public/application/app.js index 483b2f5b97e27..714887b039a42 100644 --- a/x-pack/plugins/remote_clusters/public/application/app.js +++ b/x-pack/plugins/remote_clusters/public/application/app.js @@ -6,9 +6,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { Switch, Route, Redirect, withRouter } from 'react-router-dom'; +import { Switch, Route, Redirect, Router } from 'react-router-dom'; -import { CRUD_APP_BASE_PATH, UIM_APP_LOAD } from './constants'; +import { UIM_APP_LOAD } from './constants'; import { registerRouter, setUserHasLeftApp, trackUiMetric, METRIC_TYPE } from './services'; import { RemoteClusterList, RemoteClusterAdd, RemoteClusterEdit } from './sections'; @@ -22,15 +22,16 @@ class AppComponent extends Component { constructor(...args) { super(...args); + setUserHasLeftApp(false); this.registerRouter(); } registerRouter() { // Share the router with the app without requiring React or context. - const { history, location } = this.props; + const { history } = this.props; registerRouter({ history, - route: { location }, + route: { location: history.location }, }); } @@ -45,17 +46,16 @@ class AppComponent extends Component { render() { return ( -
+ - - - - - + + + + -
+ ); } } -export const App = withRouter(AppComponent); +export const App = AppComponent; diff --git a/x-pack/plugins/remote_clusters/public/application/index.d.ts b/x-pack/plugins/remote_clusters/public/application/index.d.ts index b021dca51bacd..8b2af65e4fff7 100644 --- a/x-pack/plugins/remote_clusters/public/application/index.d.ts +++ b/x-pack/plugins/remote_clusters/public/application/index.d.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ScopedHistory } from 'kibana/public'; import { RegisterManagementAppArgs, I18nStart } from '../types'; export declare const renderApp: ( @@ -11,5 +12,6 @@ export declare const renderApp: ( I18nContext: I18nStart['Context'], appDependencies: { isCloudEnabled?: boolean; - } + }, + history: ScopedHistory ) => ReturnType; diff --git a/x-pack/plugins/remote_clusters/public/application/index.js b/x-pack/plugins/remote_clusters/public/application/index.js index cf6e855ba58df..25e171c9ef51d 100644 --- a/x-pack/plugins/remote_clusters/public/application/index.js +++ b/x-pack/plugins/remote_clusters/public/application/index.js @@ -6,7 +6,6 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { App } from './app'; @@ -15,14 +14,12 @@ import { AppContextProvider } from './app_context'; import './_hacks.scss'; -export const renderApp = (elem, I18nContext, appDependencies) => { +export const renderApp = (elem, I18nContext, appDependencies, history) => { render( - - - + , diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js index f5053e3e18ccf..b13e833f60b18 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js @@ -10,7 +10,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageContent } from '@elastic/eui'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { getRouter, redirect, extractQueryParams } from '../../services'; import { setBreadcrumbs } from '../../services/breadcrumb'; import { RemoteClusterPageTitle, RemoteClusterForm } from '../components'; @@ -49,7 +48,7 @@ export class RemoteClusterAdd extends PureComponent { const decodedRedirect = decodeURIComponent(redirectUrl); redirect(decodedRedirect); } else { - history.push(CRUD_APP_BASE_PATH); + history.push('/list'); } }; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js index 5e3b2f12a57fd..9018647600b8d 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js @@ -20,8 +20,8 @@ import { EuiTextColor, } from '@elastic/eui'; -import { CRUD_APP_BASE_PATH } from '../../constants'; -import { extractQueryParams, getRouter, getRouterLinkProps, redirect } from '../../services'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { extractQueryParams, getRouter, redirect } from '../../services'; import { setBreadcrumbs } from '../../services/breadcrumb'; import { RemoteClusterPageTitle, RemoteClusterForm, ConfiguredByNodeWarning } from '../components'; @@ -89,7 +89,7 @@ export class RemoteClusterEdit extends Component { const decodedRedirect = decodeURIComponent(redirectUrl); redirect(decodedRedirect); } else { - history.push(CRUD_APP_BASE_PATH); + history.push('/list'); openDetailPanel(clusterName); } }; @@ -143,7 +143,7 @@ export class RemoteClusterEdit extends Component { diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js index 22c986c203a04..03be45c760244 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js @@ -30,12 +30,11 @@ import { EuiTextColor, EuiTitle, } from '@elastic/eui'; - -import { CRUD_APP_BASE_PATH } from '../../../constants'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { PROXY_MODE } from '../../../../../common/constants'; -import { getRouterLinkProps } from '../../../services'; import { ConfiguredByNodeWarning } from '../../components'; import { ConnectionStatus, RemoveClusterButtonProvider } from '../components'; +import { getRouter } from '../../../services'; import { proxyModeUrl } from '../../../services/documentation'; export class DetailPanel extends Component { @@ -114,7 +113,8 @@ export class DetailPanel extends Component { renderClusterWithDeprecatedSettingWarning( { hasDeprecatedProxySetting, isConfiguredByNode }, - clusterName + clusterName, + history ) { if (!hasDeprecatedProxySetting) { return null; @@ -156,7 +156,7 @@ export class DetailPanel extends Component { defaultMessage="{editLink} to update the settings." values={{ editLink: ( - + {this.renderClusterConfiguredByNodeWarning(cluster)} - {this.renderClusterWithDeprecatedSettingWarning(cluster, clusterName)} + {this.renderClusterWithDeprecatedSettingWarning(cluster, clusterName, history)} {this.renderCluster(cluster)} )} @@ -465,7 +465,7 @@ export class DetailPanel extends Component { ); } - renderFlyoutFooter() { + renderFlyoutFooter(history) { const { cluster, clusterName, closeDetailPanel } = this.props; return ( @@ -507,7 +507,7 @@ export class DetailPanel extends Component { - {this.renderFlyoutBody()} + {this.renderFlyoutBody(history)} - {this.renderFlyoutFooter()} + {this.renderFlyoutFooter(history)} ); } diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js index 207aa8045c011..6d40cbbeb82ae 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js @@ -28,8 +28,8 @@ import { EuiCallOut, } from '@elastic/eui'; -import { CRUD_APP_BASE_PATH } from '../../constants'; -import { getRouterLinkProps, extractQueryParams } from '../../services'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { extractQueryParams } from '../../services'; import { setBreadcrumbs } from '../../services/breadcrumb'; import { RemoteClusterTable } from './remote_cluster_table'; @@ -99,7 +99,7 @@ export class RemoteClusterList extends Component { {isAuthorized && ( @@ -185,7 +185,7 @@ export class RemoteClusterList extends Component { } actions={ { @@ -94,6 +94,7 @@ export class RemoteClusterTable extends Component { render() { const { openDetailPanel } = this.props; const { selectedItems, filteredClusters } = this.state; + const { history } = getRouter(); const columns = [ { @@ -256,7 +257,7 @@ export class RemoteClusterTable extends Component { iconType="pencil" color="primary" isDisabled={isConfiguredByNode} - {...getRouterLinkProps(`${CRUD_APP_BASE_PATH}/edit/${name}`)} + {...reactRouterNavigate(history, `/edit/${name}`)} disabled={isConfiguredByNode} /> diff --git a/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts b/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts index f90a0d3456166..feec7d523e7c1 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts @@ -6,8 +6,6 @@ import { i18n } from '@kbn/i18n'; -import { CRUD_APP_BASE_PATH } from '../constants'; - interface Breadcrumb { text: string; href?: string; @@ -28,7 +26,7 @@ export function init(setGlobalBreadcrumbs: (breadcrumbs: Breadcrumb[]) => void): text: i18n.translate('xpack.remoteClusters.listBreadcrumbTitle', { defaultMessage: 'Remote Clusters', }), - href: `#${CRUD_APP_BASE_PATH}/list`, + href: `/list`, }, add: { text: i18n.translate('xpack.remoteClusters.addBreadcrumbTitle', { diff --git a/x-pack/plugins/remote_clusters/public/application/services/index.js b/x-pack/plugins/remote_clusters/public/application/services/index.js index 387a04b6e5d8c..ce8d06b6e2278 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/index.js +++ b/x-pack/plugins/remote_clusters/public/application/services/index.js @@ -14,12 +14,6 @@ export { isAddressValid, isPortValid } from './validate_address'; export { extractQueryParams } from './query_params'; -export { - setUserHasLeftApp, - getUserHasLeftApp, - registerRouter, - getRouter, - getRouterLinkProps, -} from './routing'; +export { setUserHasLeftApp, getUserHasLeftApp, registerRouter, getRouter } from './routing'; export { trackUiMetric, METRIC_TYPE } from './ui_metric'; diff --git a/x-pack/plugins/remote_clusters/public/application/services/redirect.ts b/x-pack/plugins/remote_clusters/public/application/services/redirect.ts index 00a97fa74c5ce..1130dbc77fc75 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/redirect.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/redirect.ts @@ -13,5 +13,5 @@ export function init(_navigateToApp: CoreStart['application']['navigateToApp']) } export function redirect(path: string) { - navigateToApp('kibana', { path: `#${path}` }); + navigateToApp('management', { path }); } diff --git a/x-pack/plugins/remote_clusters/public/application/services/routing.js b/x-pack/plugins/remote_clusters/public/application/services/routing.js index 6e60f75fd8bb3..c86c9756cfcc8 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/routing.js +++ b/x-pack/plugins/remote_clusters/public/application/services/routing.js @@ -8,8 +8,6 @@ * This file based on guidance from https://github.com/elastic/eui/blob/master/wiki/react-router.md */ -import { createLocation } from 'history'; - let _userHasLeftApp = false; export function setUserHasLeftApp(userHasLeftApp) { @@ -20,11 +18,6 @@ export function getUserHasLeftApp() { return _userHasLeftApp; } -const isModifiedEvent = (event) => - !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); - -const isLeftClickEvent = (event) => event.button === 0; - let router; export function registerRouter(reactRouter) { router = reactRouter; @@ -33,35 +26,3 @@ export function registerRouter(reactRouter) { export function getRouter() { return router; } - -/** - * The logic for generating hrefs and onClick handlers from the `to` prop is largely borrowed from - * https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js. - */ -export function getRouterLinkProps(to) { - const location = - typeof to === 'string' ? createLocation(to, null, null, router.history.location) : to; - - const href = router.history.createHref(location); - - const onClick = (event) => { - if (event.defaultPrevented) { - return; - } - - // If target prop is set (e.g. to "_blank"), let browser handle link. - if (event.target.getAttribute('target')) { - return; - } - - if (isModifiedEvent(event) || !isLeftClickEvent(event)) { - return; - } - - // Prevent regular link behavior, which causes a browser refresh. - event.preventDefault(); - router.history.push(location); - }; - - return { href, onClick }; -} diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js b/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js index 17523ceda54b5..d57fd37e791a1 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js @@ -6,7 +6,6 @@ import { i18n } from '@kbn/i18n'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { addCluster as sendAddClusterRequest, getRouter, @@ -108,7 +107,7 @@ export const addCluster = (cluster) => async (dispatch) => { // This will open the new job in the detail panel. Note that we're *not* showing a success toast // here, because it would partially obscure the detail panel. history.push({ - pathname: `${CRUD_APP_BASE_PATH}/list`, + pathname: `/list`, search: `?cluster=${cluster.name}`, }); } diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js b/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js index 436e6bdce36ed..4fd8faeb7021e 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { toasts, fatalError } from '../../services/notification'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { loadClusters } from './load_clusters'; import { @@ -95,7 +94,7 @@ export const editCluster = (cluster) => async (dispatch) => { // This will open the edited cluster in the detail panel. Note that we're *not* showing a success toast // here, because it would partially obscure the detail panel. history.push({ - pathname: `${CRUD_APP_BASE_PATH}/list`, + pathname: `/list`, search: `?cluster=${cluster.name}`, }); } diff --git a/x-pack/plugins/remote_clusters/public/plugin.ts b/x-pack/plugins/remote_clusters/public/plugin.ts index fde8ffa511319..8881db0f9196e 100644 --- a/x-pack/plugins/remote_clusters/public/plugin.ts +++ b/x-pack/plugins/remote_clusters/public/plugin.ts @@ -41,7 +41,7 @@ export class RemoteClustersUIPlugin defaultMessage: 'Remote Clusters', }), order: 7, - mount: async ({ element, setBreadcrumbs }) => { + mount: async ({ element, setBreadcrumbs, history }) => { const [core] = await getStartServices(); const { i18n: { Context: i18nContext }, @@ -59,7 +59,7 @@ export class RemoteClustersUIPlugin const isCloudEnabled = Boolean(cloud?.isCloudEnabled); const { renderApp } = await import('./application'); - return renderApp(element, i18nContext, { isCloudEnabled }); + return renderApp(element, i18nContext, { isCloudEnabled }, history); }, }); } diff --git a/x-pack/plugins/reporting/constants.ts b/x-pack/plugins/reporting/constants.ts index 9a1d0cec2cf96..772c52dde4a15 100644 --- a/x-pack/plugins/reporting/constants.ts +++ b/x-pack/plugins/reporting/constants.ts @@ -12,7 +12,7 @@ export const API_BASE_URL = '/api/reporting'; export const API_LIST_URL = `${API_BASE_URL}/jobs`; export const API_BASE_GENERATE = `${API_BASE_URL}/generate`; export const API_GENERATE_IMMEDIATE = `${API_BASE_URL}/v1/generate/immediate/csv/saved-object`; -export const REPORTING_MANAGEMENT_HOME = '/app/kibana#/management/insightsAndAlerting/reporting'; +export const REPORTING_MANAGEMENT_HOME = '/app/management/insightsAndAlerting/reporting'; // Statuses export const JOB_STATUS_FAILED = 'failed'; diff --git a/x-pack/plugins/reporting/public/components/report_listing.tsx b/x-pack/plugins/reporting/public/components/report_listing.tsx index c79373665d056..afcae93a8db16 100644 --- a/x-pack/plugins/reporting/public/components/report_listing.tsx +++ b/x-pack/plugins/reporting/public/components/report_listing.tsx @@ -266,7 +266,7 @@ class ReportListingUi extends Component { } catch (fetchError) { if (!this.licenseAllowsToShowThisPage()) { this.props.toasts.addDanger(this.state.badLicenseMessage); - this.props.redirect('kibana#/management'); + this.props.redirect('management'); return; } diff --git a/x-pack/plugins/reporting/public/plugin.tsx b/x-pack/plugins/reporting/public/plugin.tsx index 7495e46de47d9..8f59a898b8e35 100644 --- a/x-pack/plugins/reporting/public/plugin.tsx +++ b/x-pack/plugins/reporting/public/plugin.tsx @@ -111,7 +111,7 @@ export class ReportingPublicPlugin implements Plugin { defaultMessage: 'Manage your reports generated from Discover, Visualize, and Dashboard.', }), icon: 'reportingApp', - path: '/app/kibana#/management/kibana/reporting', + path: '/app/management/kibana/reporting', showOnHomePage: false, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/rollup/public/application.tsx b/x-pack/plugins/rollup/public/application.tsx index 1bdf940d746b2..16a0312341118 100644 --- a/x-pack/plugins/rollup/public/application.tsx +++ b/x-pack/plugins/rollup/public/application.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { ChromeBreadcrumb, CoreSetup } from 'kibana/public'; +import { CoreSetup } from 'kibana/public'; import { render, unmountComponentAtNode } from 'react-dom'; import { Provider } from 'react-redux'; import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; @@ -16,28 +16,28 @@ import { App } from './crud_app/app'; import './index.scss'; +import { ManagementAppMountParams } from '../../../../src/plugins/management/public'; + /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. */ export const renderApp = async ( core: CoreSetup, - { - element, - setBreadcrumbs, - }: { element: HTMLElement; setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void } + { history, element, setBreadcrumbs }: ManagementAppMountParams ) => { const [coreStart] = await core.getStartServices(); const I18nContext = coreStart.i18n.Context; + const services = { + history, + setBreadcrumbs, + }; + render( - + - + , diff --git a/x-pack/plugins/rollup/public/crud_app/app.js b/x-pack/plugins/rollup/public/crud_app/app.js index 0ef3253eeb94e..4eff849776aef 100644 --- a/x-pack/plugins/rollup/public/crud_app/app.js +++ b/x-pack/plugins/rollup/public/crud_app/app.js @@ -6,10 +6,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { HashRouter, Switch, Route, Redirect, withRouter } from 'react-router-dom'; +import { Router, Switch, Route, Redirect, withRouter } from 'react-router-dom'; import { UIM_APP_LOAD } from '../../common'; -import { CRUD_APP_BASE_PATH } from './constants'; import { registerRouter, setUserHasLeftApp, METRIC_TYPE } from './services'; import { trackUiMetric } from '../kibana_services'; import { JobList, JobCreate } from './sections'; @@ -53,15 +52,15 @@ export class App extends Component { render() { return ( - + - - - + + + - + ); } } diff --git a/x-pack/plugins/rollup/public/crud_app/constants/index.js b/x-pack/plugins/rollup/public/crud_app/constants/index.js index f3a218fc3b493..132affafea87d 100644 --- a/x-pack/plugins/rollup/public/crud_app/constants/index.js +++ b/x-pack/plugins/rollup/public/crud_app/constants/index.js @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { CRUD_APP_BASE_PATH } from './paths'; - export { METRICS_CONFIG } from './metrics_config'; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js index 011becded148c..85cd6e742d27f 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js @@ -27,7 +27,6 @@ import { import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { getRouterLinkProps, extractQueryParams, listBreadcrumb } from '../../services'; import { JobTable } from './job_table'; @@ -166,7 +165,7 @@ export class JobListUi extends Component { actions={ @@ -210,7 +209,7 @@ export class JobListUi extends Component { {this.getHeaderSection()} - + async (dispatch) => { // This will open the new job in the detail panel. Note that we're *not* showing a success toast // here, because it would partially obscure the detail panel. getRouter().history.push({ - pathname: `${CRUD_APP_BASE_PATH}/job_list`, + pathname: `/job_list`, search: `?job=${jobConfig.id}`, }); }; diff --git a/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js b/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js index 2012ec2248fbd..b8495a1c95a8e 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js +++ b/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js @@ -6,7 +6,6 @@ import { getRouter, getUserHasLeftApp } from '../../services'; import { CLONE_JOB_START } from '../action_types'; -import { CRUD_APP_BASE_PATH } from '../../constants'; export const cloneJob = () => (next) => (action) => { const { type } = action; @@ -14,7 +13,7 @@ export const cloneJob = () => (next) => (action) => { if (type === CLONE_JOB_START) { if (!getUserHasLeftApp()) { getRouter().history.push({ - pathname: `${CRUD_APP_BASE_PATH}/create`, + pathname: `/create`, }); } } diff --git a/x-pack/plugins/rollup/public/plugin.ts b/x-pack/plugins/rollup/public/plugin.ts index b2e793d7e75e9..b55760c5cc5aa 100644 --- a/x-pack/plugins/rollup/public/plugin.ts +++ b/x-pack/plugins/rollup/public/plugin.ts @@ -16,8 +16,6 @@ import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../src/plugins/home/public'; -// @ts-ignore -import { CRUD_APP_BASE_PATH } from './crud_app/constants'; import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { IndexManagementPluginSetup } from '../../index_management/public'; import { IndexPatternManagementSetup } from '../../../../src/plugins/index_pattern_management/public'; @@ -71,7 +69,7 @@ export class RollupPlugin implements Plugin { 'Summarize and store historical data in a smaller index for future analysis.', }), icon: 'indexRollupApp', - path: `#${CRUD_APP_BASE_PATH}/job_list`, + path: `/app/management/data/rollup_jobs/job_list`, showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js index 380275df05ba8..53a3af38f3235 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js @@ -8,7 +8,6 @@ import { mockHttpRequest, pageHelpers, nextTick } from './helpers'; import { JOB_TO_CLONE, JOB_CLONE_INDEX_PATTERN_CHECK } from './helpers/constants'; import { getRouter } from '../../crud_app/services/routing'; import { setHttp } from '../../crud_app/services'; -import { CRUD_APP_BASE_PATH } from '../../crud_app/constants'; import { coreMock } from '../../../../../../src/core/public/mocks'; jest.mock('lodash/function/debounce', () => (fn) => fn); @@ -65,8 +64,8 @@ describe('Smoke test cloning an existing rollup job from job list', () => { find('jobActionMenuButton').simulate('click'); - expect(router.history.location.pathname).not.toBe(`${CRUD_APP_BASE_PATH}/create`); + expect(router.history.location.pathname).not.toBe(`/create`); find('jobCloneActionContextMenu').simulate('click'); - expect(router.history.location.pathname).toBe(`${CRUD_APP_BASE_PATH}/create`); + expect(router.history.location.pathname).toBe(`/create`); }); }); diff --git a/x-pack/plugins/security/common/licensing/license_service.test.ts b/x-pack/plugins/security/common/licensing/license_service.test.ts index 77e6460b7669a..564b71a2e0fac 100644 --- a/x-pack/plugins/security/common/licensing/license_service.test.ts +++ b/x-pack/plugins/security/common/licensing/license_service.test.ts @@ -5,7 +5,7 @@ */ import { of, BehaviorSubject } from 'rxjs'; -import { licensingMock } from '../../../licensing/public/mocks'; +import { licenseMock } from '../../../licensing/common/licensing.mock'; import { SecurityLicenseService } from './license_service'; describe('license features', function () { @@ -29,7 +29,7 @@ describe('license features', function () { }); it('should display error when X-Pack is unavailable', () => { - const rawLicenseMock = licensingMock.createLicenseMock(); + const rawLicenseMock = licenseMock.createLicenseMock(); rawLicenseMock.isAvailable = false; const serviceSetup = new SecurityLicenseService().setup({ license$: of(rawLicenseMock), @@ -50,7 +50,7 @@ describe('license features', function () { }); it('should notify consumers of licensed feature changes', () => { - const rawLicenseMock = licensingMock.createLicenseMock(); + const rawLicenseMock = licenseMock.createLicenseMock(); rawLicenseMock.isAvailable = false; const rawLicense$ = new BehaviorSubject(rawLicenseMock); const serviceSetup = new SecurityLicenseService().setup({ @@ -79,7 +79,7 @@ describe('license features', function () { ] `); - rawLicense$.next(licensingMock.createLicenseMock()); + rawLicense$.next(licenseMock.createLicenseMock()); expect(subscriptionHandler).toHaveBeenCalledTimes(2); expect(subscriptionHandler.mock.calls[1]).toMatchInlineSnapshot(` Array [ @@ -103,7 +103,7 @@ describe('license features', function () { }); it('should show login page and other security elements, allow RBAC but forbid paid features if license is basic.', () => { - const mockRawLicense = licensingMock.createLicense({ + const mockRawLicense = licenseMock.createLicense({ features: { security: { isEnabled: true, isAvailable: true } }, }); @@ -129,7 +129,7 @@ describe('license features', function () { }); it('should not show login page or other security elements if security is disabled in Elasticsearch.', () => { - const mockRawLicense = licensingMock.createLicense({ + const mockRawLicense = licenseMock.createLicense({ features: { security: { isEnabled: false, isAvailable: true } }, }); @@ -151,7 +151,7 @@ describe('license features', function () { }); it('should allow role mappings, access agreement and sub-feature privileges, but not DLS/FLS if license = gold', () => { - const mockRawLicense = licensingMock.createLicense({ + const mockRawLicense = licenseMock.createLicense({ license: { mode: 'gold', type: 'gold' }, features: { security: { isEnabled: true, isAvailable: true } }, }); @@ -174,7 +174,7 @@ describe('license features', function () { }); it('should allow to login, allow RBAC, role mappings, access agreement, sub-feature privileges, and DLS if license >= platinum', () => { - const mockRawLicense = licensingMock.createLicense({ + const mockRawLicense = licenseMock.createLicense({ license: { mode: 'platinum', type: 'platinum' }, features: { security: { isEnabled: true, isAvailable: true } }, }); @@ -197,7 +197,7 @@ describe('license features', function () { }); it('should allow all basic features + audit logging for standard license', () => { - const mockRawLicense = licensingMock.createLicense({ + const mockRawLicense = licenseMock.createLicense({ license: { mode: 'standard', type: 'standard' }, features: { security: { isEnabled: true, isAvailable: true } }, }); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx index 0500abcd2206a..94f9de010cc2a 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx @@ -64,10 +64,13 @@ describe('APIKeysGridPage', () => { }); }); + const coreStart = coreMock.createStart(); + const getViewProperties = () => { - const { docLinks, notifications } = coreMock.createStart(); + const { docLinks, notifications, application } = coreStart; return { docLinks: new DocumentationLinksService(docLinks), + navigateToApp: application.navigateToApp, notifications, apiKeysAPIClient: apiClientMock, }; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx index 8308a66e2d900..1ee1adf41a156 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx @@ -26,7 +26,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment-timezone'; -import { NotificationsStart } from 'src/core/public'; +import { ApplicationStart, NotificationsStart } from 'src/core/public'; import { SectionLoading } from '../../../../../../../src/plugins/es_ui_shared/public'; import { ApiKey, ApiKeyToInvalidate } from '../../../../common/model'; import { APIKeysAPIClient } from '../api_keys_api_client'; @@ -40,6 +40,7 @@ interface Props { notifications: NotificationsStart; docLinks: DocumentationLinksService; apiKeysAPIClient: PublicMethodsOf; + navigateToApp: ApplicationStart['navigateToApp']; } interface State { @@ -137,7 +138,11 @@ export class APIKeysGridPage extends Component { if (!isLoadingTable && apiKeys && apiKeys.length === 0) { return ( - + ); } diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx index ef1ac40ca4b32..9b2ccfcb99ef3 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx @@ -5,6 +5,8 @@ */ import React, { Fragment } from 'react'; +import { ApplicationStart } from 'kibana/public'; + import { EuiEmptyPrompt, EuiButton, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { DocumentationLinksService } from '../../documentation_links'; @@ -12,9 +14,14 @@ import { DocumentationLinksService } from '../../documentation_links'; interface Props { isAdmin: boolean; docLinks: DocumentationLinksService; + navigateToApp: ApplicationStart['navigateToApp']; } -export const EmptyPrompt: React.FunctionComponent = ({ isAdmin, docLinks }) => ( +export const EmptyPrompt: React.FunctionComponent = ({ + isAdmin, + docLinks, + navigateToApp, +}) => ( = ({ isAdmin, docLinks } actions={ - + navigateToApp('dev_tools')} + data-test-subj="goToConsoleButton" + > ({ APIKeysGridPage: (props: any) => `Page: ${JSON.stringify(props)}`, })); - +import { ScopedHistory } from 'src/core/public'; import { apiKeysManagementApp } from './api_keys_management_app'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; describe('apiKeysManagementApp', () => { it('create() returns proper management app descriptor', () => { @@ -37,10 +37,11 @@ describe('apiKeysManagementApp', () => { basePath: '/some-base-path', element: container, setBreadcrumbs, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, }); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '#/some-base-path', text: 'API Keys' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '/', text: 'API Keys' }]); expect(container).toMatchInlineSnapshot(`
Page: {"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"apiKeysAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}}} diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx index b9ec5b35b3f9d..6ff91852d0a3e 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx @@ -25,18 +25,18 @@ export const apiKeysManagementApp = Object.freeze({ title: i18n.translate('xpack.security.management.apiKeysTitle', { defaultMessage: 'API Keys', }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs }) { setBreadcrumbs([ { text: i18n.translate('xpack.security.apiKeys.breadcrumb', { defaultMessage: 'API Keys', }), - href: `#${basePath}`, + href: `/`, }, ]); const [ - [{ docLinks, http, notifications, i18n: i18nStart }], + [{ docLinks, http, notifications, i18n: i18nStart, application }], { APIKeysGridPage }, { APIKeysAPIClient }, ] = await Promise.all([ @@ -48,6 +48,7 @@ export const apiKeysManagementApp = Object.freeze({ render( { describe('setup()', () => { it('properly registers security section and its applications', () => { @@ -24,11 +30,10 @@ describe('ManagementService', () => { const { authc } = securityMock.createSetup(); const license = licenseMock.create(); - const mockSection = { registerApp: jest.fn() }; - const managementSetup = { + const managementSetup: ManagementSetup = { sections: { + register: jest.fn(), getSection: jest.fn().mockReturnValue(mockSection), - getAllSections: jest.fn(), }, }; @@ -80,17 +85,20 @@ describe('ManagementService', () => { license.features$ = licenseSubject; const service = new ManagementService(); + + const managementSetup: ManagementSetup = { + sections: { + register: jest.fn(), + getSection: jest.fn().mockReturnValue(mockSection), + }, + }; + service.setup({ getStartServices: getStartServices as any, license, fatalErrors, authc: securityMock.createSetup().authc, - management: { - sections: { - getSection: jest.fn().mockReturnValue({ registerApp: jest.fn() }), - getAllSections: jest.fn(), - }, - }, + management: managementSetup, }); const getMockedApp = () => { @@ -115,17 +123,18 @@ describe('ManagementService', () => { [roleMappingsManagementApp.id, getMockedApp()], ] as Array<[string, jest.Mocked]>); - service.start({ - management: { - sections: { - getSection: jest - .fn() - .mockReturnValue({ getApp: jest.fn().mockImplementation((id) => mockApps.get(id)) }), - getAllSections: jest.fn(), - navigateToApp: jest.fn(), - }, - legacy: undefined, + const managementStart: ManagementStart = { + sections: { + getSection: jest + .fn() + .mockReturnValue({ getApp: jest.fn().mockImplementation((id) => mockApps.get(id)) }), + getAllSections: jest.fn(), + getSectionsEnabled: jest.fn(), }, + }; + + service.start({ + management: managementStart, }); return { diff --git a/x-pack/plugins/security/public/management/management_urls.ts b/x-pack/plugins/security/public/management/management_urls.ts index 0d4e3fc920bdb..493f6c64317a5 100644 --- a/x-pack/plugins/security/public/management/management_urls.ts +++ b/x-pack/plugins/security/public/management/management_urls.ts @@ -4,20 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -const MANAGEMENT_PATH = '/management'; -const SECURITY_PATH = `${MANAGEMENT_PATH}/security`; -export const ROLES_PATH = `${SECURITY_PATH}/roles`; -export const EDIT_ROLES_PATH = `${ROLES_PATH}/edit`; -export const CLONE_ROLES_PATH = `${ROLES_PATH}/clone`; -export const USERS_PATH = `${SECURITY_PATH}/users`; -export const EDIT_USERS_PATH = `${USERS_PATH}/edit`; -export const ROLE_MAPPINGS_PATH = `${SECURITY_PATH}/role_mappings`; -const CREATE_ROLE_MAPPING_PATH = `${ROLE_MAPPINGS_PATH}/edit`; - -export const getEditRoleHref = (roleName: string) => - `#${ROLES_PATH}/edit/${encodeURIComponent(roleName)}`; - -export const getCreateRoleMappingHref = () => `#${CREATE_ROLE_MAPPING_PATH}`; +export const EDIT_ROLE_MAPPING_PATH = `/edit`; export const getEditRoleMappingHref = (roleMappingName: string) => - `#${CREATE_ROLE_MAPPING_PATH}/${encodeURIComponent(roleMappingName)}`; + `${EDIT_ROLE_MAPPING_PATH}/${encodeURIComponent(roleMappingName)}`; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx index eea6bbef94306..b4e755507f8c5 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx @@ -12,6 +12,7 @@ import { findTestSubject } from 'test_utils/find_test_subject'; // This is not required for the tests to pass, but it rather suppresses lengthy // warnings in the console which adds unnecessary noise to the test output. import 'test_utils/stub_web_worker'; +import { ScopedHistory } from 'kibana/public'; import { EditRoleMappingPage } from '.'; import { NoCompatibleRealms, SectionLoading, PermissionDenied } from '../components'; @@ -21,13 +22,15 @@ import { RolesAPIClient } from '../../roles'; import { Role } from '../../../../common/model'; import { DocumentationLinksService } from '../documentation_links'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { roleMappingsAPIClientMock } from '../role_mappings_api_client.mock'; import { rolesAPIClientMock } from '../../roles/roles_api_client.mock'; import { RoleComboBox } from '../../role_combo_box'; describe('EditRoleMappingPage', () => { + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; let rolesAPI: PublicMethodsOf; + beforeEach(() => { rolesAPI = rolesAPIClientMock.create(); (rolesAPI as jest.Mocked).getRoles.mockResolvedValue([ @@ -54,6 +57,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -116,6 +120,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -163,6 +168,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -190,6 +196,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -227,6 +234,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -267,6 +275,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -309,6 +318,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -363,6 +373,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -418,6 +429,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx index 3b16bd8dc44ac..b4e3627039ecb 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ScopedHistory } from 'src/core/public'; import { RoleMapping } from '../../../../common/model'; import { RuleEditorPanel } from './rule_editor_panel'; import { @@ -29,7 +29,6 @@ import { SectionLoading, } from '../components'; import { RolesAPIClient } from '../../roles'; -import { ROLE_MAPPINGS_PATH } from '../../management_urls'; import { validateRoleMappingForSave } from './services/role_mapping_validation'; import { MappingInfoPanel } from './mapping_info_panel'; import { DocumentationLinksService } from '../documentation_links'; @@ -55,6 +54,7 @@ interface Props { rolesAPIClient: PublicMethodsOf; notifications: NotificationsStart; docLinks: DocumentationLinksService; + history: ScopedHistory; } export class EditRoleMappingPage extends Component { @@ -342,7 +342,5 @@ export class EditRoleMappingPage extends Component { } } - private backToRoleMappingsList = () => { - window.location.hash = ROLE_MAPPINGS_PATH; - }; + private backToRoleMappingsList = () => this.props.history.push('/'); } diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx index 6fe4bcc7a0bbb..7330dba968162 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx @@ -7,11 +7,21 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getCreateRoleMappingHref } from '../../../management_urls'; +import { ScopedHistory } from 'kibana/public'; +import { EDIT_ROLE_MAPPING_PATH } from '../../../management_urls'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; -export const CreateRoleMappingButton = () => { +interface CreateRoleMappingButtonProps { + history: ScopedHistory; +} + +export const CreateRoleMappingButton = ({ history }: CreateRoleMappingButtonProps) => { return ( - + = () => ( +interface EmptyPromptProps { + history: ScopedHistory; +} + +export const EmptyPrompt: React.FunctionComponent = ({ history }) => ( = () => (

} - actions={} + actions={} data-test-subj="roleMappingsEmptyPrompt" /> ); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx index 0d343ad33d78e..fb81ddb641e1f 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; +import { CoreStart, ScopedHistory } from 'kibana/public'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { RoleMappingsGridPage } from '.'; import { SectionLoading, PermissionDenied, NoCompatibleRealms } from '../components'; @@ -14,11 +15,19 @@ import { EuiLink } from '@elastic/eui'; import { act } from '@testing-library/react'; import { DocumentationLinksService } from '../documentation_links'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { roleMappingsAPIClientMock } from '../role_mappings_api_client.mock'; import { rolesAPIClientMock } from '../../roles/index.mock'; describe('RoleMappingsGridPage', () => { + let history: ScopedHistory; + let coreStart: CoreStart; + + beforeEach(() => { + history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + coreStart = coreMock.createStart(); + }); + it('renders an empty prompt when no role mappings exist', async () => { const roleMappingsAPI = roleMappingsAPIClientMock.create(); roleMappingsAPI.getRoleMappings.mockResolvedValue([]); @@ -34,6 +43,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -61,6 +72,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -96,6 +109,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -130,6 +145,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); @@ -137,9 +154,7 @@ describe('RoleMappingsGridPage', () => { const links = findTestSubject(wrapper, 'roleMappingRoles').find(EuiLink); expect(links).toHaveLength(1); - expect(links.at(0).props()).toMatchObject({ - href: '#/management/security/roles/edit/superuser', - }); + expect(links.at(0).props().onClick).toBeDefined(); }); it('describes the number of mapped role templates', async () => { @@ -164,6 +179,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); @@ -202,6 +219,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); @@ -263,6 +282,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx index d0bc96b4fcedf..757e59a4e0583 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx @@ -24,7 +24,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ApplicationStart, ScopedHistory } from 'src/core/public'; import { RoleMapping, Role } from '../../../../common/model'; import { EmptyPrompt } from './empty_prompt'; import { @@ -33,18 +33,21 @@ import { PermissionDenied, SectionLoading, } from '../components'; -import { getCreateRoleMappingHref, getEditRoleMappingHref } from '../../management_urls'; +import { EDIT_ROLE_MAPPING_PATH, getEditRoleMappingHref } from '../../management_urls'; import { DocumentationLinksService } from '../documentation_links'; import { RoleMappingsAPIClient } from '../role_mappings_api_client'; import { RoleTableDisplay } from '../../role_table_display'; import { RolesAPIClient } from '../../roles'; import { EnabledBadge, DisabledBadge } from '../../badges'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { rolesAPIClient: PublicMethodsOf; roleMappingsAPI: PublicMethodsOf; notifications: NotificationsStart; docLinks: DocumentationLinksService; + history: ScopedHistory; + navigateToApp: ApplicationStart['navigateToApp']; } interface State { @@ -119,7 +122,7 @@ export class RoleMappingsGridPage extends Component { if (loadState === 'finished' && roleMappings && roleMappings.length === 0) { return ( - + ); } @@ -160,7 +163,10 @@ export class RoleMappingsGridPage extends Component { - + { render: (roleMappingName: string) => { return ( {roleMappingName} @@ -323,7 +329,13 @@ export class RoleMappingsGridPage extends Component { const role: Role | string = this.state.roles?.find((r) => r.name === rolename) ?? rolename; - return ; + return ( + + ); }); return
{roleLinks}
; }, @@ -367,7 +379,10 @@ export class RoleMappingsGridPage extends Component { iconType="pencil" color="primary" data-test-subj={`editRoleMappingButton-${record.name}`} - href={getEditRoleMappingHref(record.name)} + {...reactRouterNavigate( + this.props.history, + getEditRoleMappingHref(record.name) + )} /> ); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx index 5907413d7299e..c95d78f90f51a 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx @@ -12,17 +12,22 @@ jest.mock('./edit_role_mapping', () => ({ EditRoleMappingPage: (props: any) => `Role Mapping Edit Page: ${JSON.stringify(props)}`, })); +import { ScopedHistory } from 'src/core/public'; import { roleMappingsManagementApp } from './role_mappings_management_app'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; -import { coreMock } from '../../../../../../src/core/public/mocks'; - -async function mountApp(basePath: string) { +async function mountApp(basePath: string, pathname: string) { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); const unmount = await roleMappingsManagementApp .create({ getStartServices: coreMock.createSetup().getStartServices as any }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -44,16 +49,13 @@ describe('roleMappingsManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/role_mappings'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Role Mappings' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Role Mappings' }]); expect(container).toMatchInlineSnapshot(`
- Role Mappings Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} + Role Mappings Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}}}
`); @@ -63,19 +65,16 @@ describe('roleMappingsManagementApp', () => { }); it('mount() works for the `create role mapping` page', async () => { - const basePath = '/some-base-path/role_mappings'; - window.location.hash = `${basePath}/edit`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/edit'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Role Mappings' }, + { href: `/`, text: 'Role Mappings' }, { text: 'Create' }, ]); expect(container).toMatchInlineSnapshot(`
- Role Mapping Edit Page: {"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} + Role Mapping Edit Page: {"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
`); @@ -85,20 +84,18 @@ describe('roleMappingsManagementApp', () => { }); it('mount() works for the `edit role mapping` page', async () => { - const basePath = '/some-base-path/role_mappings'; const roleMappingName = 'someRoleMappingName'; - window.location.hash = `${basePath}/edit/${roleMappingName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${roleMappingName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Role Mappings' }, - { href: `#/some-base-path/role_mappings/edit/${roleMappingName}`, text: roleMappingName }, + { href: `/`, text: 'Role Mappings' }, + { href: `/edit/${roleMappingName}`, text: roleMappingName }, ]); expect(container).toMatchInlineSnapshot(`
- Role Mapping Edit Page: {"name":"someRoleMappingName","roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} + Role Mapping Edit Page: {"name":"someRoleMappingName","roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/someRoleMappingName","search":"","hash":""}}}
`); @@ -108,18 +105,15 @@ describe('roleMappingsManagementApp', () => { }); it('mount() properly encodes role mapping name in `edit role mapping` page link in breadcrumbs', async () => { - const basePath = '/some-base-path/role_mappings'; const roleMappingName = 'some 安全性 role mapping'; - window.location.hash = `${basePath}/edit/${roleMappingName}`; - const { setBreadcrumbs } = await mountApp(basePath); + const { setBreadcrumbs } = await mountApp('/', `/edit/${roleMappingName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Role Mappings' }, + { href: `/`, text: 'Role Mappings' }, { - href: - '#/some-base-path/role_mappings/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role%20mapping', + href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role%20mapping', text: roleMappingName, }, ]); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx index ffb6d6d98f180..bca3a070e64f9 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor } from 'src/core/public'; import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; @@ -26,13 +26,14 @@ export const roleMappingsManagementApp = Object.freeze({ title: i18n.translate('xpack.security.management.roleMappingsTitle', { defaultMessage: 'Role Mappings', }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { + const [coreStart] = await getStartServices(); const roleMappingsBreadcrumbs = [ { text: i18n.translate('xpack.security.roleMapping.breadcrumb', { defaultMessage: 'Role Mappings', }), - href: `#${basePath}`, + href: `/`, }, ]; @@ -60,6 +61,8 @@ export const roleMappingsManagementApp = Object.freeze({ rolesAPIClient={new RolesAPIClient(http)} roleMappingsAPI={roleMappingsAPIClient} docLinks={dockLinksService} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); }; @@ -70,7 +73,7 @@ export const roleMappingsManagementApp = Object.freeze({ setBreadcrumbs([ ...roleMappingsBreadcrumbs, name - ? { text: name, href: `#${basePath}/edit/${encodeURIComponent(name)}` } + ? { text: name, href: `/edit/${encodeURIComponent(name)}` } : { text: i18n.translate('xpack.security.roleMappings.createBreadcrumb', { defaultMessage: 'Create', @@ -85,15 +88,16 @@ export const roleMappingsManagementApp = Object.freeze({ rolesAPIClient={new RolesAPIClient(http)} notifications={notifications} docLinks={dockLinksService} + history={history} /> ); }; render( - + - + diff --git a/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx b/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx index 28978f0090011..c1349eba9cddc 100644 --- a/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx +++ b/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx @@ -6,19 +6,20 @@ import React from 'react'; import { EuiLink, EuiToolTip, EuiIcon } from '@elastic/eui'; +import { ApplicationStart } from 'kibana/public'; import { Role, isRoleDeprecated, getExtendedRoleDeprecationNotice } from '../../../common/model'; -import { getEditRoleHref } from '../management_urls'; interface Props { role: Role | string; + navigateToApp: ApplicationStart['navigateToApp']; } -export const RoleTableDisplay = ({ role }: Props) => { +export const RoleTableDisplay = ({ role, navigateToApp }: Props) => { let content; - let href; + let path: string; if (typeof role === 'string') { content =
{role}
; - href = getEditRoleHref(role); + path = `security/roles/edit/${encodeURIComponent(role)}`; } else if (isRoleDeprecated(role)) { content = ( {
); - href = getEditRoleHref(role.name); + path = `security/roles/edit/${encodeURIComponent(role.name)}`; } else { content =
{role.name}
; - href = getEditRoleHref(role.name); + path = `security/roles/edit/${encodeURIComponent(role.name)}`; } - return {content}; + + return navigateToApp('management', { path })}>{content}; }; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx index f1ee681331005..afb8b6ec5dbe0 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -8,7 +8,7 @@ import { ReactWrapper } from 'enzyme'; import React from 'react'; import { act } from '@testing-library/react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { Capabilities } from 'src/core/public'; +import { Capabilities, ScopedHistory } from 'src/core/public'; import { Feature } from '../../../../../features/public'; import { Role } from '../../../../common/model'; import { DocumentationLinksService } from '../documentation_links'; @@ -16,7 +16,7 @@ import { EditRolePage } from './edit_role_page'; import { SimplePrivilegeSection } from './privileges/kibana/simple_privilege_section'; import { TransformErrorSection } from './privileges/kibana/transform_error_section'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { licenseMock } from '../../../../common/licensing/index.mock'; import { userAPIClientMock } from '../../users/index.mock'; @@ -183,6 +183,7 @@ function getProps({ fatalErrors, spacesEnabled, uiCapabilities: buildUICapabilities(canManageSpaces), + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, }; } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx index 3688f31be219b..77f4455d813c6 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -26,6 +26,7 @@ import React, { Fragment, FunctionComponent, HTMLProps, + useCallback, useEffect, useRef, useState, @@ -37,6 +38,7 @@ import { IHttpFetchError, NotificationsStart, } from 'src/core/public'; +import { ScopedHistory } from 'kibana/public'; import { FeaturesPluginStart } from '../../../../../features/public'; import { Feature } from '../../../../../features/common'; import { IndexPatternsContract } from '../../../../../../../src/plugins/data/public'; @@ -53,7 +55,6 @@ import { RoleIndexPrivilege, getExtendedRoleDeprecationNotice, } from '../../../../common/model'; -import { ROLES_PATH } from '../../management_urls'; import { RoleValidationResult, RoleValidator } from './validate_role'; import { DeleteRoleButton } from './delete_role_button'; import { ElasticsearchPrivileges, KibanaPrivilegesRegion } from './privileges'; @@ -65,6 +66,7 @@ import { IndicesAPIClient } from '../indices_api_client'; import { RolesAPIClient } from '../roles_api_client'; import { PrivilegesAPIClient } from '../privileges_api_client'; import { KibanaPrivileges } from '../model'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { action: 'edit' | 'clone'; @@ -82,6 +84,7 @@ interface Props { uiCapabilities: Capabilities; notifications: NotificationsStart; fatalErrors: FatalErrorsSetup; + history: ScopedHistory; } function useRunAsUsers( @@ -156,6 +159,7 @@ function useRole( notifications: NotificationsStart, license: SecurityLicense, action: string, + backToRoleList: () => void, roleName?: string ) { const [role, setRole] = useState(null); @@ -216,7 +220,7 @@ function useRole( fatalErrors.add(err); } }); - }, [roleName, action, fatalErrors, rolesAPIClient, notifications, license]); + }, [roleName, action, fatalErrors, rolesAPIClient, notifications, license, backToRoleList]); return [role, setRole] as [Role | null, typeof setRole]; } @@ -263,10 +267,6 @@ function useFeatures( return features; } -function backToRoleList() { - window.location.hash = ROLES_PATH; -} - export const EditRolePage: FunctionComponent = ({ userAPIClient, indexPatterns, @@ -283,7 +283,10 @@ export const EditRolePage: FunctionComponent = ({ docLinks, uiCapabilities, notifications, + history, }) => { + const backToRoleList = useCallback(() => history.push('/'), [history]); + // We should keep the same mutable instance of Validator for every re-render since we'll // eventually enable validation after the first time user tries to save a role. const { current: validator } = useRef(new RoleValidator({ shouldValidate: false })); @@ -300,6 +303,7 @@ export const EditRolePage: FunctionComponent = ({ notifications, license, action, + backToRoleList, roleName ); @@ -460,7 +464,7 @@ export const EditRolePage: FunctionComponent = ({ const getReturnToRoleListButton = () => { return ( - + = ({
{getFormTitle()} - - {description} - {isRoleReserved && ( @@ -584,7 +585,6 @@ export const EditRolePage: FunctionComponent = ({ )} - {isDeprecatedRole && ( @@ -595,17 +595,11 @@ export const EditRolePage: FunctionComponent = ({ /> )} - - {getRoleName()} - {getElasticsearchPrivileges()} - {getKibanaPrivileges()} - - {getFormButtons()}
diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx index e0a7d96d1cf72..743510d45107e 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx @@ -12,10 +12,11 @@ import { RolesAPIClient } from '../roles_api_client'; import { PermissionDenied } from './permission_denied'; import { RolesGridPage } from './roles_grid_page'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { rolesAPIClientMock } from '../index.mock'; import { ReservedBadge, DisabledBadge } from '../../badges'; import { findTestSubject } from 'test_utils/find_test_subject'; +import { ScopedHistory } from 'kibana/public'; const mock403 = () => ({ body: { statusCode: 403 } }); @@ -41,7 +42,10 @@ const waitForRender = async ( describe('', () => { let apiClientMock: jest.Mocked>; + let history: ScopedHistory; + beforeEach(() => { + history = (scopedHistoryMock.create() as unknown) as ScopedHistory; apiClientMock = rolesAPIClientMock.create(); apiClientMock.getRoles.mockResolvedValue([ { @@ -68,6 +72,7 @@ describe('', () => { const wrapper = mountWithIntl( ); @@ -85,6 +90,7 @@ describe('', () => { const wrapper = mountWithIntl( ); @@ -104,6 +110,7 @@ describe('', () => { const wrapper = mountWithIntl( ); @@ -117,6 +124,7 @@ describe('', () => { const wrapper = mountWithIntl( ); @@ -146,6 +154,7 @@ describe('', () => { const wrapper = mountWithIntl( ); diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx index 4f0d7ca8621a3..051c16f03d342 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx @@ -26,6 +26,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { NotificationsStart } from 'src/core/public'; +import { ScopedHistory } from 'kibana/public'; import { Role, isRoleEnabled, @@ -38,10 +39,12 @@ import { RolesAPIClient } from '../roles_api_client'; import { ConfirmDelete } from './confirm_delete'; import { PermissionDenied } from './permission_denied'; import { DisabledBadge, DeprecatedBadge, ReservedBadge } from '../../badges'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { notifications: NotificationsStart; rolesAPIClient: PublicMethodsOf; + history: ScopedHistory; } interface State { @@ -55,7 +58,7 @@ interface State { } const getRoleManagementHref = (action: 'edit' | 'clone', roleName?: string) => { - return `#/management/security/roles/${action}${roleName ? `/${roleName}` : ''}`; + return `/${action}${roleName ? `/${roleName}` : ''}`; }; export class RolesGridPage extends Component { @@ -106,7 +109,10 @@ export class RolesGridPage extends Component {
- + { render: (name: string, record: Role) => { return ( - + {name} @@ -228,7 +237,10 @@ export class RolesGridPage extends Component { title={title} color={'primary'} iconType={'pencil'} - href={getRoleManagementHref('edit', role.name)} + {...reactRouterNavigate( + this.props.history, + getRoleManagementHref('edit', role.name) + )} /> ); }, @@ -248,7 +260,10 @@ export class RolesGridPage extends Component { title={title} color={'primary'} iconType={'copy'} - href={getRoleManagementHref('clone', role.name)} + {...reactRouterNavigate( + this.props.history, + getRoleManagementHref('edit', role.name) + )} /> ); }, diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx index 96051dbd7fa56..e7f38c86b045e 100644 --- a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx @@ -14,12 +14,14 @@ jest.mock('./edit_role', () => ({ EditRolePage: (props: any) => `Role Edit Page: ${JSON.stringify(props)}`, })); +import { ScopedHistory } from 'src/core/public'; + import { rolesManagementApp } from './roles_management_app'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; import { featuresPluginMock } from '../../../../features/public/mocks'; -async function mountApp(basePath: string) { +async function mountApp(basePath: string, pathname: string) { const { fatalErrors } = coreMock.createSetup(); const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); @@ -34,7 +36,12 @@ async function mountApp(basePath: string) { .fn() .mockResolvedValue([coreMock.createStart(), { data: {}, features: featuresStart }]), }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -60,16 +67,13 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/roles'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Roles' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }]); expect(container).toMatchInlineSnapshot(`
- Roles Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}}} + Roles Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}}}
`); @@ -79,19 +83,13 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `create role` page', async () => { - const basePath = '/some-base-path/roles'; - window.location.hash = `${basePath}/edit`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/edit'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, - { text: 'Create' }, - ]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }, { text: 'Create' }]); expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} + Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
`); @@ -101,20 +99,18 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `edit role` page', async () => { - const basePath = '/some-base-path/roles'; const roleName = 'someRoleName'; - window.location.hash = `${basePath}/edit/${roleName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${roleName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, - { href: `#/some-base-path/roles/edit/${roleName}`, text: roleName }, + { href: `/`, text: 'Roles' }, + { href: `/edit/${roleName}`, text: roleName }, ]); expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"edit","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} + Role Edit Page: {"action":"edit","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/someRoleName","search":"","hash":""}}}
`); @@ -124,20 +120,15 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `clone role` page', async () => { - const basePath = '/some-base-path/roles'; const roleName = 'someRoleName'; - window.location.hash = `${basePath}/clone/${roleName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/clone/${roleName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, - { text: 'Create' }, - ]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }, { text: 'Create' }]); expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} + Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/clone/someRoleName","search":"","hash":""}}}
`); @@ -147,17 +138,15 @@ describe('rolesManagementApp', () => { }); it('mount() properly encodes role name in `edit role` page link in breadcrumbs', async () => { - const basePath = '/some-base-path/roles'; const roleName = 'some 安全性 role'; - window.location.hash = `${basePath}/edit/${roleName}`; - const { setBreadcrumbs } = await mountApp(basePath); + const { setBreadcrumbs } = await mountApp('/', `/edit/${roleName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, + { href: `/`, text: 'Roles' }, { - href: '#/some-base-path/roles/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role', + href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role', text: roleName, }, ]); diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx index 9aaa3b47f3b19..8891809d0b934 100644 --- a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor, FatalErrorsSetup } from 'src/core/public'; import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; @@ -27,11 +27,11 @@ export const rolesManagementApp = Object.freeze({ id: this.id, order: 20, title: i18n.translate('xpack.security.management.rolesTitle', { defaultMessage: 'Roles' }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { const rolesBreadcrumbs = [ { text: i18n.translate('xpack.security.roles.breadcrumb', { defaultMessage: 'Roles' }), - href: `#${basePath}`, + href: `/`, }, ]; @@ -59,7 +59,13 @@ export const rolesManagementApp = Object.freeze({ const rolesAPIClient = new RolesAPIClient(http); const RolesGridPageWithBreadcrumbs = () => { setBreadcrumbs(rolesBreadcrumbs); - return ; + return ( + + ); }; const EditRolePageWithBreadcrumbs = ({ action }: { action: 'edit' | 'clone' }) => { @@ -68,7 +74,7 @@ export const rolesManagementApp = Object.freeze({ setBreadcrumbs([ ...rolesBreadcrumbs, action === 'edit' && roleName - ? { text: roleName, href: `#${basePath}/edit/${encodeURIComponent(roleName)}` } + ? { text: roleName, href: `/edit/${encodeURIComponent(roleName)}` } : { text: i18n.translate('xpack.security.roles.createBreadcrumb', { defaultMessage: 'Create', @@ -95,15 +101,16 @@ export const rolesManagementApp = Object.freeze({ docLinks={new DocumentationLinksService(docLinks)} uiCapabilities={application.capabilities} indexPatterns={data.indexPatterns} + history={history} /> ); }; render( - + - + diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx index a97781ba25ea6..7ee33357b9af4 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx @@ -5,12 +5,13 @@ */ import { act } from '@testing-library/react'; +import { ScopedHistory } from 'kibana/public'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { EditUserPage } from './edit_user_page'; import React from 'react'; import { User, Role } from '../../../../common/model'; import { ReactWrapper } from 'enzyme'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { mockAuthenticatedUser } from '../../../../common/model/authenticated_user.mock'; import { securityMock } from '../../../mocks'; import { rolesAPIClientMock } from '../../roles/index.mock'; @@ -103,6 +104,8 @@ function expectMissingSaveButton(wrapper: ReactWrapper) { } describe('EditUserPage', () => { + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + it('allows reserved users to be viewed', async () => { const user = createUser('reserved_user'); const { apiClient, rolesAPIClient } = buildClients(user); @@ -114,6 +117,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -136,6 +140,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -158,6 +163,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -182,6 +188,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -204,6 +211,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx index 49da4c66a7630..eea7edd62fbfa 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx @@ -30,10 +30,9 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ScopedHistory } from 'src/core/public'; import { User, EditUser, Role, isRoleDeprecated } from '../../../../common/model'; import { AuthenticationServiceSetup } from '../../../authentication'; -import { USERS_PATH } from '../../management_urls'; import { RolesAPIClient } from '../../roles'; import { ConfirmDeleteUsers, ChangePasswordForm } from '../components'; import { UserValidator, UserValidationResult } from './validate_user'; @@ -47,6 +46,7 @@ interface Props { rolesAPIClient: PublicMethodsOf; authc: AuthenticationServiceSetup; notifications: NotificationsStart; + history: ScopedHistory; } interface State { @@ -61,10 +61,6 @@ interface State { formError: UserValidationResult | null; } -function backToUserList() { - window.location.hash = USERS_PATH; -} - export class EditUserPage extends Component { private validator: UserValidator; @@ -102,6 +98,10 @@ export class EditUserPage extends Component { } } + private backToUserList() { + this.props.history.push('/'); + } + private async setCurrentUser() { const { username, userAPIClient, rolesAPIClient, notifications, authc } = this.props; let { user, currentUser } = this.state; @@ -120,7 +120,7 @@ export class EditUserPage extends Component { }), text: get(err, 'body.message') || err.message, }); - return backToUserList(); + return this.backToUserList(); } } @@ -148,7 +148,7 @@ export class EditUserPage extends Component { private handleDelete = (usernames: string[], errors: string[]) => { if (errors.length === 0) { - backToUserList(); + this.backToUserList(); } }; @@ -184,7 +184,7 @@ export class EditUserPage extends Component { ) ); - backToUserList(); + this.backToUserList(); } catch (e) { this.props.notifications.toasts.addDanger( i18n.translate('xpack.security.management.users.editUser.savingUserErrorMessage', { @@ -549,7 +549,7 @@ export class EditUserPage extends Component { {reserved && ( - + this.backToUserList()}> {
- + this.backToUserList()} + > { + let history: ScopedHistory; + let coreStart: CoreStart; + + beforeEach(() => { + history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + history.createHref = (location: LocationDescriptorObject) => { + return `${location.pathname}${location.search ? '?' + location.search : ''}`; + }; + coreStart = coreMock.createStart(); + }); + it('renders the list of users', async () => { const apiClientMock = userAPIClientMock.create(); apiClientMock.getUsers.mockImplementation(() => { @@ -44,7 +57,9 @@ describe('UsersGridPage', () => { ); @@ -64,7 +79,9 @@ describe('UsersGridPage', () => { ); @@ -93,7 +110,9 @@ describe('UsersGridPage', () => { ); @@ -125,7 +144,9 @@ describe('UsersGridPage', () => { ); @@ -173,7 +194,9 @@ describe('UsersGridPage', () => { ); @@ -231,7 +254,9 @@ describe('UsersGridPage', () => { ); diff --git a/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx index 5fd2b4ec85589..50815808c4859 100644 --- a/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx +++ b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx @@ -23,19 +23,22 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ApplicationStart, ScopedHistory } from 'src/core/public'; import { User, Role } from '../../../../common/model'; import { ConfirmDeleteUsers } from '../components'; import { isUserReserved, getExtendedUserDeprecationNotice, isUserDeprecated } from '../user_utils'; import { DisabledBadge, ReservedBadge, DeprecatedBadge } from '../../badges'; import { RoleTableDisplay } from '../../role_table_display'; import { RolesAPIClient } from '../../roles'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { UserAPIClient } from '..'; interface Props { userAPIClient: PublicMethodsOf; rolesAPIClient: PublicMethodsOf; notifications: NotificationsStart; + history: ScopedHistory; + navigateToApp: ApplicationStart['navigateToApp']; } interface State { @@ -70,6 +73,7 @@ export class UsersGridPage extends Component { public render() { const { users, roles, permissionDenied, showDeleteConfirmation, selection } = this.state; + if (permissionDenied) { return ( @@ -97,7 +101,6 @@ export class UsersGridPage extends Component { ); } - const path = '#/management/security/'; const columns: Array> = [ { field: 'username', @@ -107,7 +110,10 @@ export class UsersGridPage extends Component { sortable: true, truncateText: true, render: (username: string) => ( - + {username} ), @@ -144,7 +150,13 @@ export class UsersGridPage extends Component { render: (rolenames: string[]) => { const roleLinks = rolenames.map((rolename, index) => { const roleDefinition = roles?.find((role) => role.name === rolename) ?? rolename; - return ; + return ( + + ); }); return
{roleLinks}
; }, @@ -219,7 +231,10 @@ export class UsersGridPage extends Component { - + ({ EditUserPage: (props: any) => `User Edit Page: ${JSON.stringify(props)}`, })); +import { ScopedHistory } from 'src/core/public'; import { usersManagementApp } from './users_management_app'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; import { securityMock } from '../../mocks'; -async function mountApp(basePath: string) { +async function mountApp(basePath: string, pathname: string) { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); @@ -26,7 +27,12 @@ async function mountApp(basePath: string) { authc: securityMock.createSetup().authc, getStartServices: coreMock.createSetup().getStartServices as any, }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -49,16 +55,13 @@ describe('usersManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/users'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Users' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Users' }]); expect(container).toMatchInlineSnapshot(`
- Users Page: {"notifications":{"toasts":{}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}}} + Users Page: {"notifications":{"toasts":{}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}}}
`); @@ -68,19 +71,13 @@ describe('usersManagementApp', () => { }); it('mount() works for the `create user` page', async () => { - const basePath = '/some-base-path/users'; - window.location.hash = `${basePath}/edit`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/edit'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Users' }, - { text: 'Create' }, - ]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Users' }, { text: 'Create' }]); expect(container).toMatchInlineSnapshot(`
- User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}}} + User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
`); @@ -90,20 +87,18 @@ describe('usersManagementApp', () => { }); it('mount() works for the `edit user` page', async () => { - const basePath = '/some-base-path/users'; const userName = 'someUserName'; - window.location.hash = `${basePath}/edit/${userName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${userName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Users' }, - { href: `#/some-base-path/users/edit/${userName}`, text: userName }, + { href: `/`, text: 'Users' }, + { href: `/edit/${userName}`, text: userName }, ]); expect(container).toMatchInlineSnapshot(`
- User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"username":"someUserName"} + User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"username":"someUserName","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/someUserName","search":"","hash":""}}}
`); @@ -113,17 +108,15 @@ describe('usersManagementApp', () => { }); it('mount() properly encodes user name in `edit user` page link in breadcrumbs', async () => { - const basePath = '/some-base-path/users'; const username = 'some 安全性 user'; - window.location.hash = `${basePath}/edit/${username}`; - const { setBreadcrumbs } = await mountApp(basePath); + const { setBreadcrumbs } = await mountApp('/', `/edit/${username}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Users' }, + { href: `/`, text: 'Users' }, { - href: '#/some-base-path/users/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20user', + href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20user', text: username, }, ]); diff --git a/x-pack/plugins/security/public/management/users/users_management_app.tsx b/x-pack/plugins/security/public/management/users/users_management_app.tsx index 9d337c1508ad4..82c55d67b9026 100644 --- a/x-pack/plugins/security/public/management/users/users_management_app.tsx +++ b/x-pack/plugins/security/public/management/users/users_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor } from 'src/core/public'; import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; @@ -25,11 +25,12 @@ export const usersManagementApp = Object.freeze({ id: this.id, order: 10, title: i18n.translate('xpack.security.management.usersTitle', { defaultMessage: 'Users' }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { + const [coreStart] = await getStartServices(); const usersBreadcrumbs = [ { text: i18n.translate('xpack.security.users.breadcrumb', { defaultMessage: 'Users' }), - href: `#${basePath}`, + href: `/`, }, ]; @@ -56,6 +57,8 @@ export const usersManagementApp = Object.freeze({ notifications={notifications} userAPIClient={userAPIClient} rolesAPIClient={rolesAPIClient} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); }; @@ -66,7 +69,7 @@ export const usersManagementApp = Object.freeze({ setBreadcrumbs([ ...usersBreadcrumbs, username - ? { text: username, href: `#${basePath}/edit/${encodeURIComponent(username)}` } + ? { text: username, href: `/edit/${encodeURIComponent(username)}` } : { text: i18n.translate('xpack.security.users.createBreadcrumb', { defaultMessage: 'Create', @@ -81,15 +84,16 @@ export const usersManagementApp = Object.freeze({ rolesAPIClient={new RolesAPIClient(http)} notifications={notifications} username={username} + history={history} /> ); }; render( - + - + diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index 38ef552e75a9e..da69dd051c11d 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -122,7 +122,7 @@ export class SecurityPlugin 'Protect your data and easily manage who has access to what with users and roles.', }), icon: 'securityApp', - path: '/app/kibana#/management/security/users', + path: '/app/management/security/users', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/schemas.ts b/x-pack/plugins/siem/common/detection_engine/schemas/common/schemas.ts similarity index 99% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/schemas.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/common/schemas.ts index faae1dde83545..9eb2d9abccbd3 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/schemas.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/common/schemas.ts @@ -27,7 +27,7 @@ export const filters = t.array(t.unknown); // Filters are not easily type-able y /** * Params is an "object", since it is a type of AlertActionParams which is action templates. - * @see x-pack/plugins/alerting/common/alert.ts + * @see x-pack/plugins/alerts/common/alert.ts */ export const action_group = t.string; export const action_id = t.string; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/__mocks__/utils.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/__mocks__/utils.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/__mocks__/utils.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/__mocks__/utils.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.test.ts similarity index 86% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.test.ts index 9bbde3d5236db..2a4d75522d010 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.test.ts @@ -9,19 +9,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { getErrorPayload } from './__mocks__/utils'; import { errorSchema, ErrorSchema } from './error_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('error_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an error with a UUID given for id', () => { const error = getErrorPayload(); const decoded = errorSchema.decode(getErrorPayload()); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.ts index f9c776e3b3cdc..986d3ad87ec85 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/error_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { rule_id, status_code, message } from './schemas'; +import { rule_id, status_code, message } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ // We use id: t.string intentionally and _never_ the id from global schemas as diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.test.ts similarity index 93% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.test.ts index 1b7d7994462c7..51163c3d76ed6 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.test.ts @@ -9,19 +9,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { getFindResponseSingle, getBaseResponsePayload } from './__mocks__/utils'; import { left } from 'fp-ts/lib/Either'; import { RulesSchema } from './rules_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; -import { exactCheck } from '../../../../../../common/exact_check'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('find_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate a typical single find rules response', () => { const payload = getFindResponseSingle(); const decoded = findRulesSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.ts similarity index 89% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.ts index d7e8a246cfe01..77077ce2e22ac 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/find_rules_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { rulesSchema } from './rules_schema'; -import { page, perPage, total } from './schemas'; +import { page, perPage, total } from '../common/schemas'; export const findRulesSchema = t.exact( t.type({ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.test.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.test.ts index 18e17a319883a..d7efe4b30af11 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.test.ts @@ -8,20 +8,11 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left, Either } from 'fp-ts/lib/Either'; import { ImportRulesSchema, importRulesSchema } from './import_rules_schema'; import { ErrorSchema } from './error_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; import { Errors } from 'io-ts'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('import_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an empty import response with no errors', () => { const payload: ImportRulesSchema = { success: true, success_count: 0, errors: [] }; const decoded = importRulesSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.ts similarity index 91% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.ts index dec32b18e2b24..adea77e7b933f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/import_rules_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { success, success_count } from './schemas'; +import { success, success_count } from '../common/schemas'; import { errorSchema } from './error_schema'; /* eslint-enable @typescript-eslint/camelcase */ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts similarity index 90% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts index 2d3fd75914822..fc3f89996daf1 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts @@ -7,19 +7,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; import { PrePackagedRulesSchema, prePackagedRulesSchema } from './prepackaged_rules_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an empty prepackaged response with defaults', () => { const payload: PrePackagedRulesSchema = { rules_installed: 0, rules_updated: 0 }; const decoded = prePackagedRulesSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.ts similarity index 89% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.ts index f0eff0ba19753..3b0107c91fee0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { rules_installed, rules_updated } from './schemas'; +import { rules_installed, rules_updated } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ export const prePackagedRulesSchema = t.exact( diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts index abe601a546111..eeae72209829e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts @@ -10,19 +10,10 @@ import { PrePackagedRulesStatusSchema, prePackagedRulesStatusSchema, } from './prepackaged_rules_status_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate an empty prepackaged response with defaults', () => { const payload: PrePackagedRulesStatusSchema = { rules_installed: 0, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts similarity index 96% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts index 72e5821eb4697..ee8e7b48a58bc 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts @@ -12,7 +12,7 @@ import { rules_custom_installed, rules_not_installed, rules_not_updated, -} from './schemas'; +} from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ export const prePackagedRulesStatusSchema = t.exact( diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_bulk_schema.test.ts similarity index 93% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/rules_bulk_schema.test.ts index 98cb2ef058485..04cf012f36dba 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_bulk_schema.test.ts @@ -11,19 +11,10 @@ import { getBaseResponsePayload, getErrorPayload } from './__mocks__/utils'; import { RulesBulkSchema, rulesBulkSchema } from './rules_bulk_schema'; import { RulesSchema } from './rules_schema'; import { ErrorSchema } from './error_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rule_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate a regular message and and error together with a uuid', () => { const payload: RulesBulkSchema = [getBaseResponsePayload(), getErrorPayload()]; const decoded = rulesBulkSchema.decode(payload); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_bulk_schema.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/rules_bulk_schema.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.test.ts similarity index 70% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.test.ts index 0b0d3bf43b1e9..8ed9c30507f4f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.test.ts @@ -4,32 +4,209 @@ * you may not use this file except in compliance with the Elastic License. */ +import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import * as t from 'io-ts'; import { + rulesSchema, + RulesSchema, checkTypeDependents, getDependents, addSavedId, - addTimelineTitle, addQueryFields, + addTimelineTitle, addMlFields, -} from './check_type_dependents'; +} from './rules_schema'; import { getBaseResponsePayload, getMlRuleResponsePayload } from './__mocks__/utils'; -import { left } from 'fp-ts/lib/Either'; -import { RulesSchema } from './rules_schema'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; import { TypeAndTimelineOnly } from './type_timeline_only_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; -describe('check_type_dependents', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); +export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; + +describe('rules_schema', () => { + test('it should validate a type of "query" without anything extra', () => { + const payload = getBaseResponsePayload(); + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "query" when it has extra data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate invalid_data for the type', () => { + const payload: Omit & { type: string } = getBaseResponsePayload(); + payload.type = 'invalid_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "invalid_data" supplied to "type"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "query" with a saved_id together', () => { + const payload = getBaseResponsePayload(); + payload.type = 'query'; + payload.saved_id = 'save id 123'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + + expected.type = 'saved_query'; + expected.saved_id = 'save id 123'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.type = 'saved_query'; + delete payload.saved_id; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "saved_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" when it has extra data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.type = 'saved_query'; + payload.saved_id = 'save id 123'; + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + payload.timeline_title = 'some timeline title'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = getBaseResponsePayload(); + expected.timeline_id = 'some timeline id'; + expected.timeline_title = 'some timeline title'; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); }); - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); + test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { + const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + payload.timeline_title = 'some timeline title'; + payload.invalid_extra_data = 'invalid_extra_data'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_id = 'some timeline id'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "timeline_title"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { + const payload = getBaseResponsePayload(); + payload.timeline_title = 'some timeline title'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { + const payload = getBaseResponsePayload(); + payload.saved_id = 'some saved id'; + payload.type = 'saved_query'; + payload.timeline_title = 'some timeline title'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { + const payload = getBaseResponsePayload(); + payload.saved_id = 'some saved id'; + payload.type = 'saved_query'; + payload.timeline_id = 'some timeline id'; + + const decoded = rulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "timeline_title"', + ]); + expect(message.schema).toEqual({}); }); describe('checkTypeDependents', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.ts similarity index 54% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.ts index fb1ee8e670e31..a7a31ec9e1b59 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/rules_schema.ts @@ -7,10 +7,11 @@ /* eslint-disable @typescript-eslint/camelcase */ import * as t from 'io-ts'; import { isObject } from 'lodash/fp'; -import { Either, fold, right, left } from 'fp-ts/lib/Either'; - +import { Either, left, fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { checkTypeDependents } from './check_type_dependents'; +import { typeAndTimelineOnlySchema, TypeAndTimelineOnly } from './type_timeline_only_schema'; +import { isMlRule } from '../../../machine_learning/helpers'; + import { actions, anomaly_threshold, @@ -54,9 +55,8 @@ import { filters, meta, note, -} from './schemas'; +} from '../common/schemas'; import { ListsDefaultArray } from '../types/lists_default_array'; -import { hasListsFeature } from '../../../feature_flags'; /** * This is the required fields for the rules schema response. Put all required properties on @@ -155,32 +155,92 @@ export const rulesSchema = new t.Type< 'RulesSchema', (input: unknown): input is RulesWithoutTypeDependentsSchema => isObject(input), (input): Either => { - const output = checkTypeDependents(input); - if (!hasListsFeature()) { - // TODO: (LIST-FEATURE) Remove this after the lists feature is an accepted feature for a particular release - return removeList(output); - } else { - return output; - } + return checkTypeDependents(input); }, t.identity ); -// TODO: (LIST-FEATURE) Remove this after the lists feature is an accepted feature for a particular release -export const removeList = ( - decoded: Either -): Either => { - const onLeft = (errors: t.Errors): Either => left(errors); - const onRight = (decodedValue: RequiredRulesSchema): Either => { - delete decodedValue.exceptions_list; - return right(decodedValue); - }; - const folded = fold(onLeft, onRight); - return pipe(decoded, folded); -}; - /** * This is the correct type you want to use for Rules that are outputted from the * REST interface. This has all base and all optional properties merged together. */ export type RulesSchema = t.TypeOf; + +export const addSavedId = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (typeAndTimelineOnly.type === 'saved_query') { + return [t.exact(t.type({ saved_id: dependentRulesSchema.props.saved_id }))]; + } else { + return []; + } +}; + +export const addTimelineTitle = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (typeAndTimelineOnly.timeline_id != null) { + return [ + t.exact(t.type({ timeline_title: dependentRulesSchema.props.timeline_title })), + t.exact(t.type({ timeline_id: dependentRulesSchema.props.timeline_id })), + ]; + } else { + return []; + } +}; + +export const addQueryFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (typeAndTimelineOnly.type === 'query' || typeAndTimelineOnly.type === 'saved_query') { + return [ + t.exact(t.type({ query: dependentRulesSchema.props.query })), + t.exact(t.type({ language: dependentRulesSchema.props.language })), + ]; + } else { + return []; + } +}; + +export const addMlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { + if (isMlRule(typeAndTimelineOnly.type)) { + return [ + t.exact(t.type({ anomaly_threshold: dependentRulesSchema.props.anomaly_threshold })), + t.exact( + t.type({ machine_learning_job_id: dependentRulesSchema.props.machine_learning_job_id }) + ), + ]; + } else { + return []; + } +}; + +export const getDependents = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed => { + const dependents: t.Mixed[] = [ + t.exact(requiredRulesSchema), + t.exact(partialRulesSchema), + ...addSavedId(typeAndTimelineOnly), + ...addTimelineTitle(typeAndTimelineOnly), + ...addQueryFields(typeAndTimelineOnly), + ...addMlFields(typeAndTimelineOnly), + ]; + + if (dependents.length > 1) { + // This unsafe cast is because t.intersection does not use an array but rather a set of + // tuples and really does not look like they expected us to ever dynamically build up + // intersections, but here we are doing that. Looking at their code, although they limit + // the array elements to 5, it looks like you have N number of intersections + const unsafeCast: [t.Mixed, t.Mixed] = dependents as [t.Mixed, t.Mixed]; + return t.intersection(unsafeCast); + } else { + // We are not allowed to call t.intersection with a single value so we return without + // it here normally. + return dependents[0]; + } +}; + +export const checkTypeDependents = (input: unknown): Either => { + const typeOnlyDecoded = typeAndTimelineOnlySchema.decode(input); + const onLeft = (errors: t.Errors): Either => left(errors); + const onRight = ( + typeAndTimelineOnly: TypeAndTimelineOnly + ): Either => { + const intersections = getDependents(typeAndTimelineOnly); + return intersections.decode(input); + }; + return pipe(typeOnlyDecoded, fold(onLeft, onRight)); +}; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts similarity index 85% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts index 8f06e2c6e49b0..c7335ffd62f02 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts @@ -8,19 +8,10 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { TypeAndTimelineOnly, typeAndTimelineOnlySchema } from './type_timeline_only_schema'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../../common/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../exact_check'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('prepackaged_rule_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - test('it should validate a a type and timeline_id together', () => { const payload: TypeAndTimelineOnly = { type: 'query', diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.ts b/x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.ts similarity index 92% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.ts index 6d11ff03563d1..d23d4ad2e83d4 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/response/type_timeline_only_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; /* eslint-disable @typescript-eslint/camelcase */ -import { timeline_id, type } from './schemas'; +import { timeline_id, type } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ /** diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/iso_date_string.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/iso_date_string.test.ts index 9f9181359d44a..e8bce3f38f4b3 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/iso_date_string.test.ts @@ -7,7 +7,7 @@ import { IsoDateString } from './iso_date_string'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('ios_date_string', () => { test('it should validate a iso string', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/iso_date_string.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/iso_date_string.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.test.ts similarity index 98% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.test.ts index dc0bd6cacf0d6..31e0a8e5c2c73 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.test.ts @@ -7,7 +7,7 @@ import { ListsDefaultArray } from './lists_default_array'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('lists_default_array', () => { test('it should validate an empty array', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.ts similarity index 97% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.ts index 743914ad070a2..8244f4a29e193 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/lists_default_array.ts @@ -11,7 +11,7 @@ import { list_and as listAnd, list_values as listValues, list_values_operator as listOperator, -} from '../response/schemas'; +} from '../common/schemas'; export type ListsDefaultArrayC = t.Type; export type List = t.TypeOf; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer_greater_than_zero.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer_greater_than_zero.test.ts index a3338c878bd71..821eb066a6531 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer_greater_than_zero.test.ts @@ -7,7 +7,7 @@ import { PositiveIntegerGreaterThanZero } from './positive_integer_greater_than_zero'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('positive_integer_greater_than_zero', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer_greater_than_zero.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/positive_integer_greater_than_zero.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/postive_integer.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/postive_integer.test.ts index 48ea2025b9b12..ea00ecf5efe0d 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/postive_integer.test.ts @@ -7,7 +7,7 @@ import { PositiveInteger } from './positive_integer'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('positive_integer_greater_than_zero', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/references_default_array.test.ts similarity index 95% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/references_default_array.test.ts index 3aaff7e00ad51..43e2dbdac1fe1 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/references_default_array.test.ts @@ -7,7 +7,7 @@ import { ReferencesDefaultArray } from './references_default_array'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('references_default_array', () => { test('it should validate an empty array', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/references_default_array.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/references_default_array.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/risk_score.test.ts similarity index 96% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/risk_score.test.ts index 41c0faf4d608d..cf849f28a0963 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/risk_score.test.ts @@ -7,7 +7,7 @@ import { RiskScore } from './risk_score'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('risk_score', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/risk_score.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/risk_score.ts diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/uuid.test.ts similarity index 94% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/uuid.test.ts index b640b449e6b8a..d3a68a7575487 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts +++ b/x-pack/plugins/siem/common/detection_engine/schemas/types/uuid.test.ts @@ -7,7 +7,7 @@ import { UUID } from './uuid'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { foldLeftRight, getPaths } from '../../../test_utils'; describe('uuid', () => { test('it should validate a uuid', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.ts b/x-pack/plugins/siem/common/detection_engine/schemas/types/uuid.ts similarity index 100% rename from x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.ts rename to x-pack/plugins/siem/common/detection_engine/schemas/types/uuid.ts diff --git a/x-pack/plugins/siem/common/detection_engine/transform_actions.ts b/x-pack/plugins/siem/common/detection_engine/transform_actions.ts index 4ce3823575833..7c15bc143e0fd 100644 --- a/x-pack/plugins/siem/common/detection_engine/transform_actions.ts +++ b/x-pack/plugins/siem/common/detection_engine/transform_actions.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../alerting/common'; +import { AlertAction } from '../../../alerts/common'; import { RuleAlertAction } from './types'; export const transformRuleToAlertAction = ({ diff --git a/x-pack/plugins/siem/common/detection_engine/types.ts b/x-pack/plugins/siem/common/detection_engine/types.ts index 5a91cfd4809c6..431d716a9f205 100644 --- a/x-pack/plugins/siem/common/detection_engine/types.ts +++ b/x-pack/plugins/siem/common/detection_engine/types.ts @@ -5,7 +5,7 @@ */ import * as t from 'io-ts'; -import { AlertAction } from '../../../alerting/common'; +import { AlertAction } from '../../../alerts/common'; export type RuleAlertAction = Omit & { action_type_id: string; diff --git a/x-pack/plugins/siem/common/endpoint/generate_data.ts b/x-pack/plugins/siem/common/endpoint/generate_data.ts index a683db86dc6a0..597ad4df64dfe 100644 --- a/x-pack/plugins/siem/common/endpoint/generate_data.ts +++ b/x-pack/plugins/siem/common/endpoint/generate_data.ts @@ -845,10 +845,6 @@ export class EndpointDocGenerator { }, ], id: this.commonInfo.endpoint.policy.id, - policy: { - id: this.commonInfo.endpoint.policy.id, - version: policyVersion, - }, response: { configurations: { events: { diff --git a/x-pack/plugins/siem/common/endpoint/types.ts b/x-pack/plugins/siem/common/endpoint/types.ts index 6d04f1dfac38f..45b5cf2526e12 100644 --- a/x-pack/plugins/siem/common/endpoint/types.ts +++ b/x-pack/plugins/siem/common/endpoint/types.ts @@ -685,10 +685,6 @@ export interface HostPolicyResponse { id: string; status: HostPolicyResponseActionStatus; actions: HostPolicyResponseAppliedAction[]; - policy: { - id: string; - version: string; - }; response: { configurations: { malware: HostPolicyResponseConfigurationStatus; diff --git a/x-pack/plugins/siem/common/endpoint_alerts/types.ts b/x-pack/plugins/siem/common/endpoint_alerts/types.ts index 2df92b43ab52a..3fbde79414aa0 100644 --- a/x-pack/plugins/siem/common/endpoint_alerts/types.ts +++ b/x-pack/plugins/siem/common/endpoint_alerts/types.ts @@ -15,28 +15,9 @@ import { AlertEvent, KbnConfigSchemaInputTypeOf, AppLocation, + Immutable, } from '../endpoint/types'; -/** - * A deep readonly type that will make all children of a given object readonly recursively - */ -export type Immutable = T extends undefined | null | boolean | string | number - ? T - : unknown extends T - ? unknown - : T extends Array - ? ImmutableArray - : T extends Map - ? ImmutableMap - : T extends Set - ? ImmutableSet - : ImmutableObject; - -type ImmutableArray = ReadonlyArray>; -type ImmutableMap = ReadonlyMap, Immutable>; -type ImmutableSet = ReadonlySet>; -type ImmutableObject = { readonly [K in keyof T]: Immutable }; - /** * Values for the Alert APIs 'order' and 'direction' parameters. */ diff --git a/x-pack/plugins/siem/common/typed_json.ts b/x-pack/plugins/siem/common/typed_json.ts index 62e7319e091cb..61c1093002192 100644 --- a/x-pack/plugins/siem/common/typed_json.ts +++ b/x-pack/plugins/siem/common/typed_json.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { JsonObject } from '../../../../src/plugins/kibana_utils/public'; +import { JsonObject } from '../../../../src/plugins/kibana_utils/common'; export type ESQuery = ESRangeQuery | ESQueryStringQuery | ESMatchQuery | ESTermQuery | JsonObject; diff --git a/x-pack/plugins/siem/cypress/README.md b/x-pack/plugins/siem/cypress/README.md index d84c66fec1c3a..b50924532726c 100644 --- a/x-pack/plugins/siem/cypress/README.md +++ b/x-pack/plugins/siem/cypress/README.md @@ -176,16 +176,16 @@ The current archives can be found in `x-pack/test/siem_cypress/es_archives/`. - siem-kibana - siem-es - jessie -- closed_signals - - Set of data with 108 closed signals linked to "Signals test" custom rule. +- closed_alerts + - Set of data with 108 closed alerts linked to "Alerts test" custom rule. - custome_rules - Set if data with just 4 custom activated rules. - empty_kibana - Empty kibana board. - prebuilt_rules_loaded - Elastic prebuilt loaded rules and deactivated. -- signals - - Set of data with 108 opened signals linked to "Signals test" custom rule. +- alerts + - Set of data with 108 opened alerts linked to "Alerts test" custom rule. ### How to generate a new archive diff --git a/x-pack/plugins/siem/cypress/integration/detections.spec.ts b/x-pack/plugins/siem/cypress/integration/detections.spec.ts index 91727595708f6..23e84070e93ae 100644 --- a/x-pack/plugins/siem/cypress/integration/detections.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/detections.spec.ts @@ -4,24 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ import { - NUMBER_OF_SIGNALS, - OPEN_CLOSE_SIGNALS_BTN, - SELECTED_SIGNALS, - SHOWING_SIGNALS, - SIGNALS, + NUMBER_OF_ALERTS, + OPEN_CLOSE_ALERTS_BTN, + SELECTED_ALERTS, + SHOWING_ALERTS, + ALERTS, } from '../screens/detections'; import { - closeFirstSignal, - closeSignals, - goToClosedSignals, - goToOpenedSignals, - openFirstSignal, - openSignals, - selectNumberOfSignals, - waitForSignalsPanelToBeLoaded, - waitForSignals, - waitForSignalsToBeLoaded, + closeFirstAlert, + closeAlerts, + goToClosedAlerts, + goToOpenedAlerts, + openFirstAlert, + openAlerts, + selectNumberOfAlerts, + waitForAlertsPanelToBeLoaded, + waitForAlerts, + waitForAlertsToBeLoaded, } from '../tasks/detections'; import { esArchiverLoad } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -29,179 +29,176 @@ import { loginAndWaitForPage } from '../tasks/login'; import { DETECTIONS } from '../urls/navigation'; describe('Detections', () => { - context('Closing signals', () => { + context('Closing alerts', () => { beforeEach(() => { - esArchiverLoad('signals'); + esArchiverLoad('alerts'); loginAndWaitForPage(DETECTIONS); }); - it('Closes and opens signals', () => { - waitForSignalsPanelToBeLoaded(); - waitForSignalsToBeLoaded(); + it('Closes and opens alerts', () => { + waitForAlertsPanelToBeLoaded(); + waitForAlertsToBeLoaded(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .then((numberOfSignals) => { - cy.get(SHOWING_SIGNALS).should('have.text', `Showing ${numberOfSignals} signals`); + .then((numberOfAlerts) => { + cy.get(SHOWING_ALERTS).should('have.text', `Showing ${numberOfAlerts} alerts`); - const numberOfSignalsToBeClosed = 3; - selectNumberOfSignals(numberOfSignalsToBeClosed); + const numberOfAlertsToBeClosed = 3; + selectNumberOfAlerts(numberOfAlertsToBeClosed); - cy.get(SELECTED_SIGNALS).should( + cy.get(SELECTED_ALERTS).should( 'have.text', - `Selected ${numberOfSignalsToBeClosed} signals` + `Selected ${numberOfAlertsToBeClosed} alerts` ); - closeSignals(); - waitForSignals(); + closeAlerts(); + waitForAlerts(); cy.reload(); - waitForSignals(); + waitForAlerts(); - const expectedNumberOfSignalsAfterClosing = +numberOfSignals - numberOfSignalsToBeClosed; - cy.get(NUMBER_OF_SIGNALS).should( + const expectedNumberOfAlertsAfterClosing = +numberOfAlerts - numberOfAlertsToBeClosed; + cy.get(NUMBER_OF_ALERTS).should( 'have.text', - expectedNumberOfSignalsAfterClosing.toString() + expectedNumberOfAlertsAfterClosing.toString() ); - cy.get(SHOWING_SIGNALS).should( + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${expectedNumberOfSignalsAfterClosing.toString()} signals` + `Showing ${expectedNumberOfAlertsAfterClosing.toString()} alerts` ); - goToClosedSignals(); - waitForSignals(); + goToClosedAlerts(); + waitForAlerts(); - cy.get(NUMBER_OF_SIGNALS).should('have.text', numberOfSignalsToBeClosed.toString()); - cy.get(SHOWING_SIGNALS).should( + cy.get(NUMBER_OF_ALERTS).should('have.text', numberOfAlertsToBeClosed.toString()); + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${numberOfSignalsToBeClosed.toString()} signals` + `Showing ${numberOfAlertsToBeClosed.toString()} alerts` ); - cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeClosed); - const numberOfSignalsToBeOpened = 1; - selectNumberOfSignals(numberOfSignalsToBeOpened); + const numberOfAlertsToBeOpened = 1; + selectNumberOfAlerts(numberOfAlertsToBeOpened); - cy.get(SELECTED_SIGNALS).should( - 'have.text', - `Selected ${numberOfSignalsToBeOpened} signal` - ); + cy.get(SELECTED_ALERTS).should('have.text', `Selected ${numberOfAlertsToBeOpened} alert`); - openSignals(); - waitForSignals(); + openAlerts(); + waitForAlerts(); cy.reload(); - waitForSignalsToBeLoaded(); - waitForSignals(); - goToClosedSignals(); - waitForSignals(); + waitForAlertsToBeLoaded(); + waitForAlerts(); + goToClosedAlerts(); + waitForAlerts(); - const expectedNumberOfClosedSignalsAfterOpened = 2; - cy.get(NUMBER_OF_SIGNALS).should( + const expectedNumberOfClosedAlertsAfterOpened = 2; + cy.get(NUMBER_OF_ALERTS).should( 'have.text', - expectedNumberOfClosedSignalsAfterOpened.toString() + expectedNumberOfClosedAlertsAfterOpened.toString() ); - cy.get(SHOWING_SIGNALS).should( + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${expectedNumberOfClosedSignalsAfterOpened.toString()} signals` + `Showing ${expectedNumberOfClosedAlertsAfterOpened.toString()} alerts` ); - cy.get(SIGNALS).should('have.length', expectedNumberOfClosedSignalsAfterOpened); + cy.get(ALERTS).should('have.length', expectedNumberOfClosedAlertsAfterOpened); - goToOpenedSignals(); - waitForSignals(); + goToOpenedAlerts(); + waitForAlerts(); - const expectedNumberOfOpenedSignals = - +numberOfSignals - expectedNumberOfClosedSignalsAfterOpened; - cy.get(SHOWING_SIGNALS).should( + const expectedNumberOfOpenedAlerts = + +numberOfAlerts - expectedNumberOfClosedAlertsAfterOpened; + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${expectedNumberOfOpenedSignals.toString()} signals` + `Showing ${expectedNumberOfOpenedAlerts.toString()} alerts` ); cy.get('[data-test-subj="server-side-event-count"]').should( 'have.text', - expectedNumberOfOpenedSignals.toString() + expectedNumberOfOpenedAlerts.toString() ); }); }); - it('Closes one signal when more than one opened signals are selected', () => { - waitForSignalsToBeLoaded(); + it('Closes one alert when more than one opened alerts are selected', () => { + waitForAlertsToBeLoaded(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .then((numberOfSignals) => { - const numberOfSignalsToBeClosed = 1; - const numberOfSignalsToBeSelected = 3; + .then((numberOfAlerts) => { + const numberOfAlertsToBeClosed = 1; + const numberOfAlertsToBeSelected = 3; - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('have.attr', 'disabled'); - selectNumberOfSignals(numberOfSignalsToBeSelected); - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('not.have.attr', 'disabled'); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('have.attr', 'disabled'); + selectNumberOfAlerts(numberOfAlertsToBeSelected); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('not.have.attr', 'disabled'); - closeFirstSignal(); + closeFirstAlert(); cy.reload(); - waitForSignalsToBeLoaded(); - waitForSignals(); + waitForAlertsToBeLoaded(); + waitForAlerts(); - const expectedNumberOfSignals = +numberOfSignals - numberOfSignalsToBeClosed; - cy.get(NUMBER_OF_SIGNALS).invoke('text').should('eq', expectedNumberOfSignals.toString()); - cy.get(SHOWING_SIGNALS) + const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeClosed; + cy.get(NUMBER_OF_ALERTS).invoke('text').should('eq', expectedNumberOfAlerts.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${expectedNumberOfSignals.toString()} signals`); + .should('eql', `Showing ${expectedNumberOfAlerts.toString()} alerts`); - goToClosedSignals(); - waitForSignals(); + goToClosedAlerts(); + waitForAlerts(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .should('eql', numberOfSignalsToBeClosed.toString()); - cy.get(SHOWING_SIGNALS) + .should('eql', numberOfAlertsToBeClosed.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${numberOfSignalsToBeClosed.toString()} signal`); - cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed); + .should('eql', `Showing ${numberOfAlertsToBeClosed.toString()} alert`); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeClosed); }); }); }); - context('Opening signals', () => { + context('Opening alerts', () => { beforeEach(() => { - esArchiverLoad('closed_signals'); + esArchiverLoad('closed_alerts'); loginAndWaitForPage(DETECTIONS); }); - it('Open one signal when more than one closed signals are selected', () => { - waitForSignals(); - goToClosedSignals(); - waitForSignalsToBeLoaded(); + it('Open one alert when more than one closed alerts are selected', () => { + waitForAlerts(); + goToClosedAlerts(); + waitForAlertsToBeLoaded(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .then((numberOfSignals) => { - const numberOfSignalsToBeOpened = 1; - const numberOfSignalsToBeSelected = 3; + .then((numberOfAlerts) => { + const numberOfAlertsToBeOpened = 1; + const numberOfAlertsToBeSelected = 3; - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('have.attr', 'disabled'); - selectNumberOfSignals(numberOfSignalsToBeSelected); - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('not.have.attr', 'disabled'); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('have.attr', 'disabled'); + selectNumberOfAlerts(numberOfAlertsToBeSelected); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('not.have.attr', 'disabled'); - openFirstSignal(); + openFirstAlert(); cy.reload(); - goToClosedSignals(); - waitForSignalsToBeLoaded(); - waitForSignals(); + goToClosedAlerts(); + waitForAlertsToBeLoaded(); + waitForAlerts(); - const expectedNumberOfSignals = +numberOfSignals - numberOfSignalsToBeOpened; - cy.get(NUMBER_OF_SIGNALS).invoke('text').should('eq', expectedNumberOfSignals.toString()); - cy.get(SHOWING_SIGNALS) + const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeOpened; + cy.get(NUMBER_OF_ALERTS).invoke('text').should('eq', expectedNumberOfAlerts.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${expectedNumberOfSignals.toString()} signals`); + .should('eql', `Showing ${expectedNumberOfAlerts.toString()} alerts`); - goToOpenedSignals(); - waitForSignals(); + goToOpenedAlerts(); + waitForAlerts(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .should('eql', numberOfSignalsToBeOpened.toString()); - cy.get(SHOWING_SIGNALS) + .should('eql', numberOfAlertsToBeOpened.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${numberOfSignalsToBeOpened.toString()} signal`); - cy.get(SIGNALS).should('have.length', numberOfSignalsToBeOpened); + .should('eql', `Showing ${numberOfAlertsToBeOpened.toString()} alert`); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeOpened); }); }); }); diff --git a/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts b/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts index 6ea34f5203adc..d3ddb2ad71e30 100644 --- a/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SIGNAL_ID } from '../screens/detections'; +import { ALERT_ID } from '../screens/detections'; import { PROVIDER_BADGE } from '../screens/timeline'; import { - expandFirstSignal, - investigateFirstSignalInTimeline, - waitForSignalsPanelToBeLoaded, + expandFirstAlert, + investigateFirstAlertInTimeline, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -19,22 +19,22 @@ import { DETECTIONS } from '../urls/navigation'; describe('Detections timeline', () => { beforeEach(() => { - esArchiverLoad('timeline_signals'); + esArchiverLoad('timeline_alerts'); loginAndWaitForPage(DETECTIONS); }); afterEach(() => { - esArchiverUnload('timeline_signals'); + esArchiverUnload('timeline_alerts'); }); - it('Investigate signal in default timeline', () => { - waitForSignalsPanelToBeLoaded(); - expandFirstSignal(); - cy.get(SIGNAL_ID) + it('Investigate alert in default timeline', () => { + waitForAlertsPanelToBeLoaded(); + expandFirstAlert(); + cy.get(ALERT_ID) .first() .invoke('text') .then((eventId) => { - investigateFirstSignalInTimeline(); + investigateFirstAlertInTimeline(); cy.get(PROVIDER_BADGE).invoke('text').should('eql', `_id: "${eventId}"`); }); }); diff --git a/x-pack/plugins/siem/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/siem/cypress/integration/events_viewer.spec.ts index 26ebaeb844825..82b4f4f0fbe34 100644 --- a/x-pack/plugins/siem/cypress/integration/events_viewer.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/events_viewer.spec.ts @@ -17,6 +17,7 @@ import { LOAD_MORE, LOCAL_EVENTS_COUNT, } from '../screens/hosts/events'; +import { HEADERS_GROUP } from '../screens/timeline'; import { closeFieldsBrowser, filterFieldsBrowser } from '../tasks/fields_browser'; import { loginAndWaitForPage } from '../tasks/login'; @@ -25,6 +26,7 @@ import { addsHostGeoCityNameToHeader, addsHostGeoCountryNameToHeader, closeModal, + dragAndDropColumn, openEventsViewerFieldsBrowser, opensInspectQueryModal, resetFields, @@ -150,4 +152,28 @@ describe('Events Viewer', () => { cy.get(LOCAL_EVENTS_COUNT).invoke('text').should('not.equal', defaultNumberOfLoadedEvents); }); }); + + context.skip('Events columns', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + openEvents(); + waitsForEventsToBeLoaded(); + }); + + afterEach(() => { + openEventsViewerFieldsBrowser(); + resetFields(); + }); + + it('re-orders columns via drag and drop', () => { + const originalColumnOrder = + '@timestampmessagehost.nameevent.moduleevent.datasetevent.actionuser.namesource.ipdestination.ip'; + const expectedOrderAfterDragAndDrop = + 'message@timestamphost.nameevent.moduleevent.datasetevent.actionuser.namesource.ipdestination.ip'; + + cy.get(HEADERS_GROUP).invoke('text').should('equal', originalColumnOrder); + dragAndDropColumn({ column: 0, newPosition: 1 }); + cy.get(HEADERS_GROUP).invoke('text').should('equal', expectedOrderAfterDragAndDrop); + }); + }); }); diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts index d07850e23f05e..e8f9411c149d4 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts @@ -10,12 +10,12 @@ import { RULE_SWITCH, SECOND_RULE, SEVENTH_RULE, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { - goToManageSignalDetectionRules, - waitForSignalsPanelToBeLoaded, - waitForSignalsIndexToBeCreated, + goToManageAlertDetectionRules, + waitForAlertsPanelToBeLoaded, + waitForAlertsIndexToBeCreated, } from '../tasks/detections'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; @@ -24,11 +24,11 @@ import { sortByActivatedRules, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRuleToBeActivated, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { DETECTIONS } from '../urls/navigation'; -describe('Signal detection rules', () => { +describe('Detection rules', () => { before(() => { esArchiverLoad('prebuilt_rules_loaded'); }); @@ -39,9 +39,9 @@ describe('Signal detection rules', () => { it('Sorts by activated rules', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); cy.get(RULE_NAME) .eq(FIFTH_RULE) diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts index 04762bbf352d2..e5cec16c48a37 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts @@ -36,7 +36,7 @@ import { RULES_TABLE, SEVERITY, SHOWING_RULES_TEXT, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { createAndActivateRule, @@ -44,9 +44,9 @@ import { fillDefineCustomRuleWithImportedQueryAndContinue, } from '../tasks/create_new_rule'; import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { changeToThreeHundredRowsPerPage, @@ -58,13 +58,13 @@ import { selectNumberOfRules, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS } from '../urls/navigation'; -describe('Signal detection rules, custom', () => { +describe('Detection rules, custom', () => { before(() => { esArchiverLoad('custom_rule_with_timeline'); }); @@ -75,9 +75,9 @@ describe('Signal detection rules, custom', () => { it('Creates and activates a new custom rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); goToCreateNewRule(); fillDefineCustomRuleWithImportedQueryAndContinue(newRule); @@ -170,9 +170,9 @@ describe('Deletes custom rules', () => { beforeEach(() => { esArchiverLoad('custom_rules'); loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); }); after(() => { diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts index aa1a111102160..4a12990438999 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts @@ -5,13 +5,13 @@ */ import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { exportFirstRule } from '../tasks/signal_detection_rules'; +import { exportFirstRule } from '../tasks/alert_detection_rules'; import { DETECTIONS } from '../urls/navigation'; @@ -33,9 +33,9 @@ describe('Export rules', () => { it('Exports a custom rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); exportFirstRule(); cy.wait('@export').then((xhr) => { cy.readFile(EXPECTED_EXPORTED_RULE_FILE_PATH).then(($expectedExportedJson) => { diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts index cb04d8117a923..fd2dff27ad359 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts @@ -34,7 +34,7 @@ import { RULES_ROW, RULES_TABLE, SEVERITY, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { createAndActivateRule, @@ -43,9 +43,9 @@ import { selectMachineLearningRuleType, } from '../tasks/create_new_rule'; import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { changeToThreeHundredRowsPerPage, @@ -54,13 +54,13 @@ import { goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS } from '../urls/navigation'; -describe('Signal detection rules, machine learning', () => { +describe('Detection rules, machine learning', () => { before(() => { esArchiverLoad('prebuilt_rules_loaded'); }); @@ -71,9 +71,9 @@ describe('Signal detection rules, machine learning', () => { it('Creates and activates a new ml rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); goToCreateNewRule(); selectMachineLearningRuleType(); diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts index 005e24dad2a16..2cd087b2ca5e1 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts @@ -10,7 +10,7 @@ import { RELOAD_PREBUILT_RULES_BTN, RULES_ROW, RULES_TABLE, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { changeToThreeHundredRowsPerPage, @@ -22,11 +22,11 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForPrebuiltDetectionRulesToBeLoaded, waitForRulesToBeLoaded, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { esArchiverLoadEmptyKibana, esArchiverUnloadEmptyKibana } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; @@ -35,7 +35,7 @@ import { DETECTIONS } from '../urls/navigation'; import { totalNumberOfPrebuiltRules } from '../objects/rule'; -describe('Signal detection rules, prebuilt rules', () => { +describe('Detection rules, prebuilt rules', () => { before(() => { esArchiverLoadEmptyKibana(); }); @@ -49,9 +49,9 @@ describe('Signal detection rules, prebuilt rules', () => { const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); loadPrebuiltDetectionRules(); waitForPrebuiltDetectionRulesToBeLoaded(); @@ -74,9 +74,9 @@ describe('Deleting prebuilt rules', () => { esArchiverLoadEmptyKibana(); loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); loadPrebuiltDetectionRules(); waitForPrebuiltDetectionRulesToBeLoaded(); diff --git a/x-pack/plugins/siem/cypress/screens/signal_detection_rules.ts b/x-pack/plugins/siem/cypress/screens/alert_detection_rules.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/signal_detection_rules.ts rename to x-pack/plugins/siem/cypress/screens/alert_detection_rules.ts diff --git a/x-pack/plugins/siem/cypress/screens/detections.ts b/x-pack/plugins/siem/cypress/screens/detections.ts index d9ffa5b5a4ab2..b915bcba2f880 100644 --- a/x-pack/plugins/siem/cypress/screens/detections.ts +++ b/x-pack/plugins/siem/cypress/screens/detections.ts @@ -4,30 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CLOSED_SIGNALS_BTN = '[data-test-subj="closedSignals"]'; +export const CLOSED_ALERTS_BTN = '[data-test-subj="closedAlerts"]'; -export const EXPAND_SIGNAL_BTN = '[data-test-subj="expand-event"]'; +export const EXPAND_ALERT_BTN = '[data-test-subj="expand-event"]'; -export const LOADING_SIGNALS_PANEL = '[data-test-subj="loading-signals-panel"]'; +export const LOADING_ALERTS_PANEL = '[data-test-subj="loading-alerts-panel"]'; -export const MANAGE_SIGNAL_DETECTION_RULES_BTN = '[data-test-subj="manage-signal-detection-rules"]'; +export const MANAGE_ALERT_DETECTION_RULES_BTN = '[data-test-subj="manage-alert-detection-rules"]'; -export const NUMBER_OF_SIGNALS = '[data-test-subj="server-side-event-count"] .euiBadge__text'; +export const NUMBER_OF_ALERTS = '[data-test-subj="server-side-event-count"] .euiBadge__text'; -export const OPEN_CLOSE_SIGNAL_BTN = '[data-test-subj="update-signal-status-button"]'; +export const OPEN_CLOSE_ALERT_BTN = '[data-test-subj="update-alert-status-button"]'; -export const OPEN_CLOSE_SIGNALS_BTN = '[data-test-subj="openCloseSignal"] button'; +export const OPEN_CLOSE_ALERTS_BTN = '[data-test-subj="openCloseAlert"] button'; -export const OPENED_SIGNALS_BTN = '[data-test-subj="openSignals"]'; +export const OPENED_ALERTS_BTN = '[data-test-subj="openAlerts"]'; -export const SELECTED_SIGNALS = '[data-test-subj="selectedSignals"]'; +export const SELECTED_ALERTS = '[data-test-subj="selectedAlerts"]'; -export const SEND_SIGNAL_TO_TIMELINE_BTN = '[data-test-subj="send-signal-to-timeline-button"]'; +export const SEND_ALERT_TO_TIMELINE_BTN = '[data-test-subj="send-alert-to-timeline-button"]'; -export const SHOWING_SIGNALS = '[data-test-subj="showingSignals"]'; +export const SHOWING_ALERTS = '[data-test-subj="showingAlerts"]'; -export const SIGNALS = '[data-test-subj="event"]'; +export const ALERTS = '[data-test-subj="event"]'; -export const SIGNAL_ID = '[data-test-subj="draggable-content-_id"]'; +export const ALERT_ID = '[data-test-subj="draggable-content-_id"]'; -export const SIGNAL_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; +export const ALERT_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; diff --git a/x-pack/plugins/siem/cypress/screens/timeline.ts b/x-pack/plugins/siem/cypress/screens/timeline.ts index ed1dc97454fb3..bb232b752994a 100644 --- a/x-pack/plugins/siem/cypress/screens/timeline.ts +++ b/x-pack/plugins/siem/cypress/screens/timeline.ts @@ -8,6 +8,11 @@ export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]'; export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; +export const DRAGGABLE_HEADER = + '[data-test-subj="headers-group"] [data-test-subj="draggable-header"]'; + +export const HEADERS_GROUP = '[data-test-subj="headers-group"]'; + export const ID_HEADER_FIELD = '[data-test-subj="timeline"] [data-test-subj="header-text-_id"]'; export const ID_FIELD = '[data-test-subj="timeline"] [data-test-subj="field-name-_id"]'; diff --git a/x-pack/plugins/siem/cypress/tasks/signal_detection_rules.ts b/x-pack/plugins/siem/cypress/tasks/alert_detection_rules.ts similarity index 98% rename from x-pack/plugins/siem/cypress/tasks/signal_detection_rules.ts rename to x-pack/plugins/siem/cypress/tasks/alert_detection_rules.ts index 6b2c4644a95d1..9710e0e808ac5 100644 --- a/x-pack/plugins/siem/cypress/tasks/signal_detection_rules.ts +++ b/x-pack/plugins/siem/cypress/tasks/alert_detection_rules.ts @@ -24,7 +24,7 @@ import { SORT_RULES_BTN, THREE_HUNDRED_ROWS, EXPORT_ACTION_BTN, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; export const activateRule = (rulePosition: number) => { cy.get(RULE_SWITCH).eq(rulePosition).click({ force: true }); diff --git a/x-pack/plugins/siem/cypress/tasks/detections.ts b/x-pack/plugins/siem/cypress/tasks/detections.ts index 9461dd5ff99cf..f53dd83635d85 100644 --- a/x-pack/plugins/siem/cypress/tasks/detections.ts +++ b/x-pack/plugins/siem/cypress/tasks/detections.ts @@ -5,66 +5,66 @@ */ import { - CLOSED_SIGNALS_BTN, - EXPAND_SIGNAL_BTN, - LOADING_SIGNALS_PANEL, - MANAGE_SIGNAL_DETECTION_RULES_BTN, - OPEN_CLOSE_SIGNAL_BTN, - OPEN_CLOSE_SIGNALS_BTN, - OPENED_SIGNALS_BTN, - SEND_SIGNAL_TO_TIMELINE_BTN, - SIGNALS, - SIGNAL_CHECKBOX, + CLOSED_ALERTS_BTN, + EXPAND_ALERT_BTN, + LOADING_ALERTS_PANEL, + MANAGE_ALERT_DETECTION_RULES_BTN, + OPEN_CLOSE_ALERT_BTN, + OPEN_CLOSE_ALERTS_BTN, + OPENED_ALERTS_BTN, + SEND_ALERT_TO_TIMELINE_BTN, + ALERTS, + ALERT_CHECKBOX, } from '../screens/detections'; import { REFRESH_BUTTON } from '../screens/siem_header'; -export const closeFirstSignal = () => { - cy.get(OPEN_CLOSE_SIGNAL_BTN).first().click({ force: true }); +export const closeFirstAlert = () => { + cy.get(OPEN_CLOSE_ALERT_BTN).first().click({ force: true }); }; -export const closeSignals = () => { - cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); +export const closeAlerts = () => { + cy.get(OPEN_CLOSE_ALERTS_BTN).click({ force: true }); }; -export const expandFirstSignal = () => { - cy.get(EXPAND_SIGNAL_BTN).first().click({ force: true }); +export const expandFirstAlert = () => { + cy.get(EXPAND_ALERT_BTN).first().click({ force: true }); }; -export const goToClosedSignals = () => { - cy.get(CLOSED_SIGNALS_BTN).click({ force: true }); +export const goToClosedAlerts = () => { + cy.get(CLOSED_ALERTS_BTN).click({ force: true }); }; -export const goToManageSignalDetectionRules = () => { - cy.get(MANAGE_SIGNAL_DETECTION_RULES_BTN).should('exist').click({ force: true }); +export const goToManageAlertDetectionRules = () => { + cy.get(MANAGE_ALERT_DETECTION_RULES_BTN).should('exist').click({ force: true }); }; -export const goToOpenedSignals = () => { - cy.get(OPENED_SIGNALS_BTN).click({ force: true }); +export const goToOpenedAlerts = () => { + cy.get(OPENED_ALERTS_BTN).click({ force: true }); }; -export const openFirstSignal = () => { - cy.get(OPEN_CLOSE_SIGNAL_BTN).first().click({ force: true }); +export const openFirstAlert = () => { + cy.get(OPEN_CLOSE_ALERT_BTN).first().click({ force: true }); }; -export const openSignals = () => { - cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); +export const openAlerts = () => { + cy.get(OPEN_CLOSE_ALERTS_BTN).click({ force: true }); }; -export const selectNumberOfSignals = (numberOfSignals: number) => { - for (let i = 0; i < numberOfSignals; i++) { - cy.get(SIGNAL_CHECKBOX).eq(i).click({ force: true }); +export const selectNumberOfAlerts = (numberOfAlerts: number) => { + for (let i = 0; i < numberOfAlerts; i++) { + cy.get(ALERT_CHECKBOX).eq(i).click({ force: true }); } }; -export const investigateFirstSignalInTimeline = () => { - cy.get(SEND_SIGNAL_TO_TIMELINE_BTN).first().click({ force: true }); +export const investigateFirstAlertInTimeline = () => { + cy.get(SEND_ALERT_TO_TIMELINE_BTN).first().click({ force: true }); }; -export const waitForSignals = () => { +export const waitForAlerts = () => { cy.get(REFRESH_BUTTON).invoke('text').should('not.equal', 'Updating'); }; -export const waitForSignalsIndexToBeCreated = () => { +export const waitForAlertsIndexToBeCreated = () => { cy.request({ url: '/api/detection_engine/index', retryOnStatusCodeFailure: true }).then( (response) => { if (response.status !== 200) { @@ -74,12 +74,12 @@ export const waitForSignalsIndexToBeCreated = () => { ); }; -export const waitForSignalsPanelToBeLoaded = () => { - cy.get(LOADING_SIGNALS_PANEL).should('exist'); - cy.get(LOADING_SIGNALS_PANEL).should('not.exist'); +export const waitForAlertsPanelToBeLoaded = () => { + cy.get(LOADING_ALERTS_PANEL).should('exist'); + cy.get(LOADING_ALERTS_PANEL).should('not.exist'); }; -export const waitForSignalsToBeLoaded = () => { - const expectedNumberOfDisplayedSignals = 25; - cy.get(SIGNALS).should('have.length', expectedNumberOfDisplayedSignals); +export const waitForAlertsToBeLoaded = () => { + const expectedNumberOfDisplayedAlerts = 25; + cy.get(ALERTS).should('have.length', expectedNumberOfDisplayedAlerts); }; diff --git a/x-pack/plugins/siem/cypress/tasks/hosts/events.ts b/x-pack/plugins/siem/cypress/tasks/hosts/events.ts index dff58b4b0e9ea..a593650989259 100644 --- a/x-pack/plugins/siem/cypress/tasks/hosts/events.ts +++ b/x-pack/plugins/siem/cypress/tasks/hosts/events.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { drag, drop } from '../common'; import { CLOSE_MODAL, EVENTS_VIEWER_FIELDS_BUTTON, @@ -15,6 +16,7 @@ import { RESET_FIELDS, SERVER_SIDE_EVENT_COUNT, } from '../../screens/hosts/events'; +import { DRAGGABLE_HEADER } from '../../screens/timeline'; export const addsHostGeoCityNameToHeader = () => { cy.get(HOST_GEO_CITY_NAME_CHECKBOX).check({ @@ -58,3 +60,24 @@ export const resetFields = () => { export const waitsForEventsToBeLoaded = () => { cy.get(SERVER_SIDE_EVENT_COUNT).should('exist').invoke('text').should('not.equal', '0'); }; + +export const dragAndDropColumn = ({ + column, + newPosition, +}: { + column: number; + newPosition: number; +}) => { + cy.get(DRAGGABLE_HEADER).first().should('exist'); + cy.get(DRAGGABLE_HEADER) + .eq(column) + .then((header) => drag(header)); + + cy.wait(3000); // wait for DOM updates before moving + + cy.get(DRAGGABLE_HEADER) + .eq(newPosition) + .then((targetPosition) => { + drop(targetPosition); + }); +}; diff --git a/x-pack/plugins/siem/kibana.json b/x-pack/plugins/siem/kibana.json index 6b43b41df8eee..df40ad4650f45 100644 --- a/x-pack/plugins/siem/kibana.json +++ b/x-pack/plugins/siem/kibana.json @@ -5,7 +5,7 @@ "configPath": ["xpack", "siem"], "requiredPlugins": [ "actions", - "alerting", + "alerts", "data", "dataEnhanced", "embeddable", diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/signals_histogram.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx similarity index 84% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/signals_histogram.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx index f921c00cdafb7..7f340b0bea37b 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/signals_histogram.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsHistogram } from './signals_histogram'; +import { AlertsHistogram } from './alerts_histogram'; jest.mock('../../../common/lib/kibana'); -describe('SignalsHistogram', () => { +describe('AlertsHistogram', () => { it('renders correctly', () => { const wrapper = shallow( - ( +export const AlertsHistogram = React.memo( ({ chartHeight = DEFAULT_CHART_HEIGHT, data, @@ -48,9 +48,9 @@ export const SignalsHistogram = React.memo( const theme = useTheme(); const chartSize: ChartSizeArray = useMemo(() => ['100%', chartHeight], [chartHeight]); - const xAxisId = 'signalsHistogramAxisX'; - const yAxisId = 'signalsHistogramAxisY'; - const id = 'signalsHistogram'; + const xAxisId = 'alertsHistogramAxisX'; + const yAxisId = 'alertsHistogramAxisY'; + const id = 'alertsHistogram'; const yAccessors = useMemo(() => ['y'], []); const splitSeriesAccessors = useMemo(() => ['g'], []); const tickFormat = useMemo(() => histogramDateTimeFormatter([from, to]), [from, to]); @@ -59,7 +59,7 @@ export const SignalsHistogram = React.memo( <> {loading && ( ( } ); -SignalsHistogram.displayName = 'SignalsHistogram'; +AlertsHistogram.displayName = 'AlertsHistogram'; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/config.ts b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/config.ts similarity index 88% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/config.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/config.ts index 2c5a1ddd9a010..5138835873812 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/config.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/config.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SignalsHistogramOption } from './types'; +import { AlertsHistogramOption } from './types'; -export const signalsHistogramOptions: SignalsHistogramOption[] = [ +export const alertsHistogramOptions: AlertsHistogramOption[] = [ { text: 'signal.rule.risk_score', value: 'signal.rule.risk_score' }, { text: 'signal.rule.severity', value: 'signal.rule.severity' }, { text: 'signal.rule.threat.tactic.name', value: 'signal.rule.threat.tactic.name' }, diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.test.tsx similarity index 89% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.test.tsx index 2758625c0d4af..bfe4cee088a02 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.test.tsx @@ -9,25 +9,25 @@ import { showInitialLoadingSpinner } from './helpers'; describe('helpers', () => { describe('showInitialLoadingSpinner', () => { test('it should (only) show the spinner during initial loading, while we are fetching data', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingSignals: true })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingAlerts: true })).toBe( true ); }); test('it should STOP showing the spinner (during initial loading) when the first data fetch completes', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingSignals: false })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingAlerts: false })).toBe( false ); }); test('it should NOT show the spinner after initial loading has completed, even if the user requests more data (e.g. by clicking Refresh)', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingSignals: true })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingAlerts: true })).toBe( false ); }); test('it should NOT show the spinner after initial loading has completed', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingSignals: false })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingAlerts: false })).toBe( false ); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.tsx similarity index 67% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.tsx index 0c9fa39e53d00..9d124201f022e 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.tsx @@ -5,21 +5,19 @@ */ import { showAllOthersBucket } from '../../../../common/constants'; -import { HistogramData, SignalsAggregation, SignalsBucket, SignalsGroupBucket } from './types'; -import { SignalSearchResponse } from '../../containers/detection_engine/signals/types'; +import { HistogramData, AlertsAggregation, AlertsBucket, AlertsGroupBucket } from './types'; +import { AlertSearchResponse } from '../../containers/detection_engine/alerts/types'; import * as i18n from './translations'; -export const formatSignalsData = ( - signalsData: SignalSearchResponse<{}, SignalsAggregation> | null -) => { - const groupBuckets: SignalsGroupBucket[] = - signalsData?.aggregations?.signalsByGrouping?.buckets ?? []; - return groupBuckets.reduce((acc, { key: group, signals }) => { - const signalsBucket: SignalsBucket[] = signals.buckets ?? []; +export const formatAlertsData = (alertsData: AlertSearchResponse<{}, AlertsAggregation> | null) => { + const groupBuckets: AlertsGroupBucket[] = + alertsData?.aggregations?.alertsByGrouping?.buckets ?? []; + return groupBuckets.reduce((acc, { key: group, alerts }) => { + const alertsBucket: AlertsBucket[] = alerts.buckets ?? []; return [ ...acc, - ...signalsBucket.map(({ key, doc_count }: SignalsBucket) => ({ + ...alertsBucket.map(({ key, doc_count }: AlertsBucket) => ({ x: key, y: doc_count, g: group, @@ -28,7 +26,7 @@ export const formatSignalsData = ( }, []); }; -export const getSignalsHistogramQuery = ( +export const getAlertsHistogramQuery = ( stackByField: string, from: number, to: number, @@ -44,7 +42,7 @@ export const getSignalsHistogramQuery = ( return { aggs: { - signalsByGrouping: { + alertsByGrouping: { terms: { field: stackByField, ...missing, @@ -54,7 +52,7 @@ export const getSignalsHistogramQuery = ( size: 10, }, aggs: { - signals: { + alerts: { date_histogram: { field: '@timestamp', fixed_interval: `${Math.floor((to - from) / 32)}ms`, @@ -87,15 +85,15 @@ export const getSignalsHistogramQuery = ( }; /** - * Returns `true` when the signals histogram initial loading spinner should be shown + * Returns `true` when the alerts histogram initial loading spinner should be shown * * @param isInitialLoading The loading spinner will only be displayed if this value is `true`, because after initial load, a different, non-spinner loading indicator is displayed - * @param isLoadingSignals When `true`, IO is being performed to request signals (for rendering in the histogram) + * @param isLoadingAlerts When `true`, IO is being performed to request alerts (for rendering in the histogram) */ export const showInitialLoadingSpinner = ({ isInitialLoading, - isLoadingSignals, + isLoadingAlerts, }: { isInitialLoading: boolean; - isLoadingSignals: boolean; -}): boolean => isInitialLoading && isLoadingSignals; + isLoadingAlerts: boolean; +}): boolean => isInitialLoading && isLoadingAlerts; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/index.test.tsx similarity index 85% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/index.test.tsx index 6578af19094df..3376df76ac6ec 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/index.test.tsx @@ -7,15 +7,15 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsHistogramPanel } from './index'; +import { AlertsHistogramPanel } from './index'; jest.mock('../../../common/lib/kibana'); jest.mock('../../../common/components/navigation/use_get_url_search'); -describe('SignalsHistogramPanel', () => { +describe('AlertsHistogramPanel', () => { it('renders correctly', () => { const wrapper = shallow( - ` position: relative; `; -const defaultTotalSignalsObj: SignalsTotal = { +const defaultTotalAlertsObj: AlertsTotal = { value: 0, relation: 'eq', }; export const DETECTIONS_HISTOGRAM_ID = 'detections-histogram'; -const ViewSignalsFlexItem = styled(EuiFlexItem)` +const ViewAlertsFlexItem = styled(EuiFlexItem)` margin-left: 24px; `; -interface SignalsHistogramPanelProps { +interface AlertsHistogramPanelProps { chartHeight?: number; - defaultStackByOption?: SignalsHistogramOption; + defaultStackByOption?: AlertsHistogramOption; deleteQuery?: ({ id }: { id: string }) => void; filters?: Filter[]; from: number; @@ -66,9 +66,9 @@ interface SignalsHistogramPanelProps { panelHeight?: number; signalIndexName: string | null; setQuery: (params: RegisterQuery) => void; - showLinkToSignals?: boolean; - showTotalSignalsCount?: boolean; - stackByOptions?: SignalsHistogramOption[]; + showLinkToAlerts?: boolean; + showTotalAlertsCount?: boolean; + stackByOptions?: AlertsHistogramOption[]; title?: string; to: number; updateDateRange: UpdateDateRange; @@ -81,10 +81,10 @@ const getHistogramOption = (fieldName: string): MatrixHistogramOption => ({ const NO_LEGEND_DATA: LegendItem[] = []; -export const SignalsHistogramPanel = memo( +export const AlertsHistogramPanel = memo( ({ chartHeight, - defaultStackByOption = signalsHistogramOptions[0], + defaultStackByOption = alertsHistogramOptions[0], deleteQuery, filters, headerChildren, @@ -95,8 +95,8 @@ export const SignalsHistogramPanel = memo( panelHeight = DEFAULT_PANEL_HEIGHT, setQuery, signalIndexName, - showLinkToSignals = false, - showTotalSignalsCount = false, + showLinkToAlerts = false, + showTotalAlertsCount = false, stackByOptions, to, title = i18n.HISTOGRAM_HEADER, @@ -106,32 +106,32 @@ export const SignalsHistogramPanel = memo( const uniqueQueryId = useMemo(() => `${DETECTIONS_HISTOGRAM_ID}-${uuid.v4()}`, []); const [isInitialLoading, setIsInitialLoading] = useState(true); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - const [totalSignalsObj, setTotalSignalsObj] = useState(defaultTotalSignalsObj); - const [selectedStackByOption, setSelectedStackByOption] = useState( + const [totalAlertsObj, setTotalAlertsObj] = useState(defaultTotalAlertsObj); + const [selectedStackByOption, setSelectedStackByOption] = useState( onlyField == null ? defaultStackByOption : getHistogramOption(onlyField) ); const { - loading: isLoadingSignals, - data: signalsData, - setQuery: setSignalsQuery, + loading: isLoadingAlerts, + data: alertsData, + setQuery: setAlertsQuery, response, request, refetch, - } = useQuerySignals<{}, SignalsAggregation>( - getSignalsHistogramQuery(selectedStackByOption.value, from, to, []), + } = useQueryAlerts<{}, AlertsAggregation>( + getAlertsHistogramQuery(selectedStackByOption.value, from, to, []), signalIndexName ); const kibana = useKibana(); const urlSearch = useGetUrlSearch(navTabs.detections); - const totalSignals = useMemo( + const totalAlerts = useMemo( () => - i18n.SHOWING_SIGNALS( - numeral(totalSignalsObj.value).format(defaultNumberFormat), - totalSignalsObj.value, - totalSignalsObj.relation === 'gte' ? '>' : totalSignalsObj.relation === 'lte' ? '<' : '' + i18n.SHOWING_ALERTS( + numeral(totalAlertsObj.value).format(defaultNumberFormat), + totalAlertsObj.value, + totalAlertsObj.relation === 'gte' ? '>' : totalAlertsObj.relation === 'lte' ? '<' : '' ), - [totalSignalsObj] + [totalAlertsObj] ); const setSelectedOptionCallback = useCallback((event: React.ChangeEvent) => { @@ -140,12 +140,12 @@ export const SignalsHistogramPanel = memo( ); }, []); - const formattedSignalsData = useMemo(() => formatSignalsData(signalsData), [signalsData]); + const formattedAlertsData = useMemo(() => formatAlertsData(alertsData), [alertsData]); const legendItems: LegendItem[] = useMemo( () => - signalsData?.aggregations?.signalsByGrouping?.buckets != null - ? signalsData.aggregations.signalsByGrouping.buckets.map((bucket, i) => ({ + alertsData?.aggregations?.alertsByGrouping?.buckets != null + ? alertsData.aggregations.alertsByGrouping.buckets.map((bucket, i) => ({ color: i < defaultLegendColors.length ? defaultLegendColors[i] : undefined, dataProviderId: escapeDataProviderId( `draggable-legend-item-${uuid.v4()}-${selectedStackByOption.value}-${bucket.key}` @@ -154,20 +154,20 @@ export const SignalsHistogramPanel = memo( value: bucket.key, })) : NO_LEGEND_DATA, - [signalsData, selectedStackByOption.value] + [alertsData, selectedStackByOption.value] ); useEffect(() => { let canceled = false; - if (!canceled && !showInitialLoadingSpinner({ isInitialLoading, isLoadingSignals })) { + if (!canceled && !showInitialLoadingSpinner({ isInitialLoading, isLoadingAlerts })) { setIsInitialLoading(false); } return () => { canceled = true; // prevent long running data fetches from updating state after unmounting }; - }, [isInitialLoading, isLoadingSignals, setIsInitialLoading]); + }, [isInitialLoading, isLoadingAlerts, setIsInitialLoading]); useEffect(() => { return () => { @@ -185,20 +185,20 @@ export const SignalsHistogramPanel = memo( dsl: [request], response: [response], }, - loading: isLoadingSignals, + loading: isLoadingAlerts, refetch, }); } - }, [setQuery, isLoadingSignals, signalsData, response, request, refetch]); + }, [setQuery, isLoadingAlerts, alertsData, response, request, refetch]); useEffect(() => { - setTotalSignalsObj( - signalsData?.hits.total ?? { + setTotalAlertsObj( + alertsData?.hits.total ?? { value: 0, relation: 'eq', } ); - }, [signalsData]); + }, [alertsData]); useEffect(() => { const converted = esQuery.buildEsQuery( @@ -211,8 +211,8 @@ export const SignalsHistogramPanel = memo( } ); - setSignalsQuery( - getSignalsHistogramQuery( + setAlertsQuery( + getAlertsHistogramQuery( selectedStackByOption.value, from, to, @@ -222,14 +222,14 @@ export const SignalsHistogramPanel = memo( }, [selectedStackByOption.value, from, to, query, filters]); const linkButton = useMemo(() => { - if (showLinkToSignals) { + if (showLinkToAlerts) { return ( - - {i18n.VIEW_SIGNALS} - + + {i18n.VIEW_ALERTS} + ); } - }, [showLinkToSignals, urlSearch]); + }, [showLinkToAlerts, urlSearch]); const titleText = useMemo(() => (onlyField == null ? title : i18n.TOP(onlyField)), [ onlyField, @@ -237,13 +237,13 @@ export const SignalsHistogramPanel = memo( ]); return ( - + @@ -264,13 +264,13 @@ export const SignalsHistogramPanel = memo( {isInitialLoading ? ( ) : ( - @@ -281,4 +281,4 @@ export const SignalsHistogramPanel = memo( } ); -SignalsHistogramPanel.displayName = 'SignalsHistogramPanel'; +AlertsHistogramPanel.displayName = 'AlertsHistogramPanel'; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/translations.ts b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/translations.ts similarity index 50% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/translations.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/translations.ts index e7b76a48c7592..91345e3d989f1 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/translations.ts @@ -7,116 +7,116 @@ import { i18n } from '@kbn/i18n'; export const STACK_BY_LABEL = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.stackByLabel', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.stackByLabel', { defaultMessage: 'Stack by', } ); export const STACK_BY_RISK_SCORES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.riskScoresDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.riskScoresDropDown', { defaultMessage: 'Risk scores', } ); export const STACK_BY_SEVERITIES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.severitiesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.severitiesDropDown', { defaultMessage: 'Severities', } ); export const STACK_BY_DESTINATION_IPS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.destinationIpsDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.destinationIpsDropDown', { defaultMessage: 'Top destination IPs', } ); export const STACK_BY_SOURCE_IPS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.sourceIpsDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.sourceIpsDropDown', { defaultMessage: 'Top source IPs', } ); export const STACK_BY_ACTIONS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventActionsDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.eventActionsDropDown', { defaultMessage: 'Top event actions', } ); export const STACK_BY_CATEGORIES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventCategoriesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.eventCategoriesDropDown', { defaultMessage: 'Top event categories', } ); export const STACK_BY_HOST_NAMES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.hostNamesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.hostNamesDropDown', { defaultMessage: 'Top host names', } ); export const STACK_BY_RULE_TYPES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.ruleTypesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.ruleTypesDropDown', { defaultMessage: 'Top rule types', } ); export const STACK_BY_RULE_NAMES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.rulesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.rulesDropDown', { defaultMessage: 'Top rules', } ); export const STACK_BY_USERS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.usersDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.usersDropDown', { defaultMessage: 'Top users', } ); export const TOP = (fieldName: string) => - i18n.translate('xpack.siem.detectionEngine.signals.histogram.topNLabel', { + i18n.translate('xpack.siem.detectionEngine.alerts.histogram.topNLabel', { values: { fieldName }, defaultMessage: `Top {fieldName}`, }); export const HISTOGRAM_HEADER = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.headerTitle', + 'xpack.siem.detectionEngine.alerts.histogram.headerTitle', { - defaultMessage: 'Signal count', + defaultMessage: 'Alert count', } ); export const ALL_OTHERS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel', + 'xpack.siem.detectionEngine.alerts.histogram.allOthersGroupingLabel', { defaultMessage: 'All others', } ); -export const VIEW_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.viewSignalsButtonLabel', +export const VIEW_ALERTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.histogram.viewAlertsButtonLabel', { - defaultMessage: 'View signals', + defaultMessage: 'View alerts', } ); -export const SHOWING_SIGNALS = ( - totalSignalsFormatted: string, - totalSignals: number, +export const SHOWING_ALERTS = ( + totalAlertsFormatted: string, + totalAlerts: number, modifier: string ) => - i18n.translate('xpack.siem.detectionEngine.signals.histogram.showingSignalsTitle', { - values: { totalSignalsFormatted, totalSignals, modifier }, + i18n.translate('xpack.siem.detectionEngine.alerts.histogram.showingAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts, modifier }, defaultMessage: - 'Showing: {modifier}{totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', + 'Showing: {modifier}{totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/types.ts b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/types.ts similarity index 70% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/types.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/types.ts index 41d58a4a7391d..0bf483f7ec927 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/types.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/types.ts @@ -6,7 +6,7 @@ import { inputsModel } from '../../../common/store'; -export interface SignalsHistogramOption { +export interface AlertsHistogramOption { text: string; value: string; } @@ -17,26 +17,26 @@ export interface HistogramData { g: string; } -export interface SignalsAggregation { - signalsByGrouping: { - buckets: SignalsGroupBucket[]; +export interface AlertsAggregation { + alertsByGrouping: { + buckets: AlertsGroupBucket[]; }; } -export interface SignalsBucket { +export interface AlertsBucket { key_as_string: string; key: number; doc_count: number; } -export interface SignalsGroupBucket { +export interface AlertsGroupBucket { key: string; - signals: { - buckets: SignalsBucket[]; + alerts: { + buckets: AlertsBucket[]; }; } -export interface SignalsTotal { +export interface AlertsTotal { value: number; relation: string; } diff --git a/x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx new file mode 100644 index 0000000000000..7d35e429bcf50 --- /dev/null +++ b/x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx @@ -0,0 +1,49 @@ +/* + * 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 { EuiLoadingSpinner } from '@elastic/eui'; +import { FormattedRelative } from '@kbn/i18n/react'; +import React, { useState, useEffect } from 'react'; + +import { useQueryAlerts } from '../../containers/detection_engine/alerts/use_query'; +import { buildLastAlertsQuery } from './query.dsl'; +import { Aggs } from './types'; + +interface AlertInfo { + ruleId?: string | null; +} + +type Return = [React.ReactNode, React.ReactNode]; + +export const useAlertInfo = ({ ruleId = null }: AlertInfo): Return => { + const [lastAlerts, setLastAlerts] = useState( + + ); + const [totalAlerts, setTotalAlerts] = useState( + + ); + + const { loading, data: alerts } = useQueryAlerts(buildLastAlertsQuery(ruleId)); + + useEffect(() => { + if (alerts != null) { + const myAlerts = alerts; + setLastAlerts( + myAlerts.aggregations?.lastSeen.value != null ? ( + + ) : null + ); + setTotalAlerts(<>{myAlerts.hits.total.value}); + } else { + setLastAlerts(null); + setTotalAlerts(null); + } + }, [loading, alerts]); + + return [lastAlerts, totalAlerts]; +}; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts b/x-pack/plugins/siem/public/alerts/components/alerts_info/query.dsl.ts similarity index 91% rename from x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_info/query.dsl.ts index 8cb07a4f8e6b5..a3972fd35bf2d 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_info/query.dsl.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const buildLastSignalsQuery = (ruleId: string | undefined | null) => { +export const buildLastAlertsQuery = (ruleId: string | undefined | null) => { const queryFilter = [ { bool: { should: [{ match: { 'signal.status': 'open' } }], minimum_should_match: 1 }, diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/types.ts b/x-pack/plugins/siem/public/alerts/components/alerts_info/types.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/signals_info/types.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_info/types.ts diff --git a/x-pack/plugins/siem/public/alerts/components/signals/actions.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.test.tsx similarity index 92% rename from x-pack/plugins/siem/public/alerts/components/signals/actions.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/actions.test.tsx index d7a8a55077340..2fa7cfeedcd15 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/actions.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.test.tsx @@ -6,9 +6,9 @@ import sinon from 'sinon'; import moment from 'moment'; -import { sendSignalToTimelineAction, determineToAndFrom } from './actions'; +import { sendAlertToTimelineAction, determineToAndFrom } from './actions'; import { - mockEcsDataWithSignal, + mockEcsDataWithAlert, defaultTimelineProps, apolloClient, mockTimelineApolloResult, @@ -19,7 +19,7 @@ import { TimelineType, TimelineStatus } from '../../../../common/types/timeline' jest.mock('apollo-client'); -describe('signals actions', () => { +describe('alert actions', () => { const anchor = '2020-03-01T17:59:46.349Z'; const unix = moment(anchor).valueOf(); let createTimeline: CreateTimeline; @@ -46,13 +46,13 @@ describe('signals actions', () => { clock.restore(); }); - describe('sendSignalToTimelineAction', () => { + describe('sendAlertToTimelineAction', () => { describe('timeline id is NOT empty string and apollo client exists', () => { test('it invokes updateTimelineIsLoading to set to true', async () => { - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); @@ -61,10 +61,10 @@ describe('signals actions', () => { }); test('it invokes createTimeline with designated timeline template if "timelineTemplate" exists', async () => { - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); const expected = { @@ -246,10 +246,10 @@ describe('signals actions', () => { }; jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResultModified); - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); // @ts-ignore @@ -275,10 +275,10 @@ describe('signals actions', () => { }; jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResultModified); - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); // @ts-ignore @@ -293,10 +293,10 @@ describe('signals actions', () => { throw new Error('Test error'); }); - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); @@ -313,16 +313,16 @@ describe('signals actions', () => { describe('timelineId is empty string', () => { test('it invokes createTimeline with timelineDefaults', async () => { const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, signal: { rule: { - ...mockEcsDataWithSignal.signal?.rule!, + ...mockEcsDataWithAlert.signal?.rule!, timeline_id: null, }, }, }; - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, ecsData: ecsDataMock, @@ -338,16 +338,16 @@ describe('signals actions', () => { describe('apolloClient is not defined', () => { test('it invokes createTimeline with timelineDefaults', async () => { const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, signal: { rule: { - ...mockEcsDataWithSignal.signal?.rule!, + ...mockEcsDataWithAlert.signal?.rule!, timeline_id: [''], }, }, }; - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ createTimeline, ecsData: ecsDataMock, updateTimelineIsLoading, @@ -363,7 +363,7 @@ describe('signals actions', () => { describe('determineToAndFrom', () => { test('it uses ecs.Data.timestamp if one is provided', () => { const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, timestamp: '2020-03-20T17:59:46.349Z', }; const result = determineToAndFrom({ ecsData: ecsDataMock }); @@ -374,7 +374,7 @@ describe('signals actions', () => { test('it uses current time timestamp if ecsData.timestamp is not provided', () => { const { timestamp, ...ecsDataMock } = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, }; const result = determineToAndFrom({ ecsData: ecsDataMock }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.tsx similarity index 80% rename from x-pack/plugins/siem/public/alerts/components/signals/actions.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/actions.tsx index c13e064bd1c3c..cde81d44bc5d6 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.tsx @@ -8,8 +8,8 @@ import dateMath from '@elastic/datemath'; import { getOr, isEmpty } from 'lodash/fp'; import moment from 'moment'; -import { updateSignalStatus } from '../../containers/detection_engine/signals/api'; -import { SendSignalToTimelineActionProps, UpdateSignalStatusActionProps } from './types'; +import { updateAlertStatus } from '../../containers/detection_engine/alerts/api'; +import { SendAlertToTimelineActionProps, UpdateAlertStatusActionProps } from './types'; import { TimelineNonEcsData, GetOneTimeline, TimelineResult, Ecs } from '../../../graphql/types'; import { oneTimelineQuery } from '../../../timelines/containers/one/index.gql_query'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; @@ -24,7 +24,7 @@ import { replaceTemplateFieldFromDataProviders, } from './helpers'; -export const getUpdateSignalsQuery = (eventIds: Readonly) => { +export const getUpdateAlertsQuery = (eventIds: Readonly) => { return { query: { bool: { @@ -44,31 +44,35 @@ export const getFilterAndRuleBounds = ( const stringFilter = data?.[0].filter((d) => d.field === 'signal.rule.filters')?.[0]?.value ?? []; const eventTimes = data - .flatMap((signal) => signal.filter((d) => d.field === 'signal.original_time')?.[0]?.value ?? []) + .flatMap((alert) => alert.filter((d) => d.field === 'signal.original_time')?.[0]?.value ?? []) .map((d) => moment(d)); return [stringFilter, moment.min(eventTimes).valueOf(), moment.max(eventTimes).valueOf()]; }; -export const updateSignalStatusAction = async ({ +export const updateAlertStatusAction = async ({ query, - signalIds, + alertIds, status, setEventsLoading, setEventsDeleted, -}: UpdateSignalStatusActionProps) => { + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, +}: UpdateAlertStatusActionProps) => { try { - setEventsLoading({ eventIds: signalIds, isLoading: true }); + setEventsLoading({ eventIds: alertIds, isLoading: true }); - const queryObject = query ? { query: JSON.parse(query) } : getUpdateSignalsQuery(signalIds); + const queryObject = query ? { query: JSON.parse(query) } : getUpdateAlertsQuery(alertIds); - await updateSignalStatus({ query: queryObject, status }); + const response = await updateAlertStatus({ query: queryObject, status }); // TODO: Only delete those that were successfully updated from updatedRules - setEventsDeleted({ eventIds: signalIds, isDeleted: true }); - } catch (e) { - // TODO: Show error toasts + setEventsDeleted({ eventIds: alertIds, isDeleted: true }); + + onAlertStatusUpdateSuccess(response.updated, status); + } catch (error) { + onAlertStatusUpdateFailure(status, error); } finally { - setEventsLoading({ eventIds: signalIds, isLoading: false }); + setEventsLoading({ eventIds: alertIds, isLoading: false }); } }; @@ -87,13 +91,13 @@ export const determineToAndFrom = ({ ecsData }: { ecsData: Ecs }) => { return { to, from }; }; -export const sendSignalToTimelineAction = async ({ +export const sendAlertToTimelineAction = async ({ apolloClient, createTimeline, ecsData, updateTimelineIsLoading, -}: SendSignalToTimelineActionProps) => { - let openSignalInBasicTimeline = true; +}: SendAlertToTimelineActionProps) => { + let openAlertInBasicTimeline = true; const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : ''; const timelineId = ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : ''; @@ -116,7 +120,7 @@ export const sendSignalToTimelineAction = async ({ if (!isEmpty(resultingTimeline)) { const timelineTemplate: TimelineResult = omitTypenameInTimeline(resultingTimeline); - openSignalInBasicTimeline = false; + openAlertInBasicTimeline = false; const { timeline } = formatTimelineResultToModel(timelineTemplate, true); const query = replaceTemplateFieldFromQuery( timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', @@ -158,12 +162,12 @@ export const sendSignalToTimelineAction = async ({ }); } } catch { - openSignalInBasicTimeline = true; + openAlertInBasicTimeline = true; updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); } } - if (openSignalInBasicTimeline) { + if (openAlertInBasicTimeline) { createTimeline({ from, timeline: { @@ -171,7 +175,7 @@ export const sendSignalToTimelineAction = async ({ dataProviders: [ { and: [], - id: `send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-${ecsData._id}`, + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-${ecsData._id}`, name: ecsData._id, enabled: true, excluded: false, diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx similarity index 68% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx index dd30bb1b0a74d..d7fabdabf8225 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsTableFilterGroup } from './index'; +import { AlertsTableFilterGroup } from './index'; -describe('SignalsTableFilterGroup', () => { +describe('AlertsTableFilterGroup', () => { it('renders correctly', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('EuiFilterButton')).toBeTruthy(); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.tsx similarity index 74% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.tsx index a8dd22863e3c9..8521170637d6f 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.tsx @@ -10,13 +10,13 @@ import * as i18n from '../translations'; export const FILTER_OPEN = 'open'; export const FILTER_CLOSED = 'closed'; -export type SignalFilterOption = typeof FILTER_OPEN | typeof FILTER_CLOSED; +export type AlertFilterOption = typeof FILTER_OPEN | typeof FILTER_CLOSED; interface Props { - onFilterGroupChanged: (filterGroup: SignalFilterOption) => void; + onFilterGroupChanged: (filterGroup: AlertFilterOption) => void; } -const SignalsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged }) => { +const AlertsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged }) => { const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const onClickOpenFilterCallback = useCallback(() => { @@ -32,23 +32,23 @@ const SignalsTableFilterGroupComponent: React.FC = ({ onFilterGroupChange return ( - {i18n.OPEN_SIGNALS} + {i18n.OPEN_ALERTS} - {i18n.CLOSED_SIGNALS} + {i18n.CLOSED_ALERTS} ); }; -export const SignalsTableFilterGroup = React.memo(SignalsTableFilterGroupComponent); +export const AlertsTableFilterGroup = React.memo(AlertsTableFilterGroupComponent); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx similarity index 76% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx index 3b43185c2c16b..543e11c9b1e69 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsUtilityBar } from './index'; +import { AlertsUtilityBar } from './index'; jest.mock('../../../../common/lib/kibana'); -describe('SignalsUtilityBar', () => { +describe('AlertsUtilityBar', () => { it('renders correctly', () => { const wrapper = shallow( - { isFilteredToOpen={false} selectAll={jest.fn()} showClearSelection={true} - updateSignalsStatus={jest.fn()} + updateAlertsStatus={jest.fn()} /> ); - expect(wrapper.find('[dataTestSubj="openCloseSignal"]')).toBeTruthy(); + expect(wrapper.find('[dataTestSubj="openCloseAlert"]')).toBeTruthy(); }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx similarity index 77% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx index e23f4ebdd3d30..68b7039690db4 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx @@ -19,10 +19,10 @@ import { import * as i18n from './translations'; import { useUiSetting$ } from '../../../../common/lib/kibana'; import { TimelineNonEcsData } from '../../../../graphql/types'; -import { UpdateSignalsStatus } from '../types'; -import { FILTER_CLOSED, FILTER_OPEN } from '../signals_filter_group'; +import { UpdateAlertsStatus } from '../types'; +import { FILTER_CLOSED, FILTER_OPEN } from '../alerts_filter_group'; -interface SignalsUtilityBarProps { +interface AlertsUtilityBarProps { canUserCRUD: boolean; hasIndexWrite: boolean; areEventsLoading: boolean; @@ -32,10 +32,10 @@ interface SignalsUtilityBarProps { selectedEventIds: Readonly>; showClearSelection: boolean; totalCount: number; - updateSignalsStatus: UpdateSignalsStatus; + updateAlertsStatus: UpdateAlertsStatus; } -const SignalsUtilityBarComponent: React.FC = ({ +const AlertsUtilityBarComponent: React.FC = ({ canUserCRUD, hasIndexWrite, areEventsLoading, @@ -45,16 +45,16 @@ const SignalsUtilityBarComponent: React.FC = ({ isFilteredToOpen, selectAll, showClearSelection, - updateSignalsStatus, + updateAlertsStatus, }) => { const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); const handleUpdateStatus = useCallback(async () => { - await updateSignalsStatus({ - signalIds: Object.keys(selectedEventIds), + await updateAlertsStatus({ + alertIds: Object.keys(selectedEventIds), status: isFilteredToOpen ? FILTER_CLOSED : FILTER_OPEN, }); - }, [selectedEventIds, updateSignalsStatus, isFilteredToOpen]); + }, [selectedEventIds, updateAlertsStatus, isFilteredToOpen]); const formattedTotalCount = numeral(totalCount).format(defaultNumberFormat); const formattedSelectedEventsCount = numeral(Object.keys(selectedEventIds).length).format( @@ -66,25 +66,25 @@ const SignalsUtilityBarComponent: React.FC = ({ - - {i18n.SHOWING_SIGNALS(formattedTotalCount, totalCount)} + + {i18n.SHOWING_ALERTS(formattedTotalCount, totalCount)} {canUserCRUD && hasIndexWrite && ( <> - - {i18n.SELECTED_SIGNALS( + + {i18n.SELECTED_ALERTS( showClearSelection ? formattedTotalCount : formattedSelectedEventsCount, showClearSelection ? totalCount : Object.keys(selectedEventIds).length )} {isFilteredToOpen @@ -104,7 +104,7 @@ const SignalsUtilityBarComponent: React.FC = ({ > {showClearSelection ? i18n.CLEAR_SELECTION - : i18n.SELECT_ALL_SIGNALS(formattedTotalCount, totalCount)} + : i18n.SELECT_ALL_ALERTS(formattedTotalCount, totalCount)} )} @@ -115,8 +115,8 @@ const SignalsUtilityBarComponent: React.FC = ({ ); }; -export const SignalsUtilityBar = React.memo( - SignalsUtilityBarComponent, +export const AlertsUtilityBar = React.memo( + AlertsUtilityBarComponent, (prevProps, nextProps) => prevProps.areEventsLoading === nextProps.areEventsLoading && prevProps.selectedEventIds === nextProps.selectedEventIds && diff --git a/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts new file mode 100644 index 0000000000000..ae5070efc21e1 --- /dev/null +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SHOWING_ALERTS = (totalAlertsFormatted: string, totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.utilityBar.showingAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts }, + defaultMessage: + 'Showing {totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const SELECTED_ALERTS = (selectedAlertsFormatted: string, selectedAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.utilityBar.selectedAlertsTitle', { + values: { selectedAlertsFormatted, selectedAlerts }, + defaultMessage: + 'Selected {selectedAlertsFormatted} {selectedAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const SELECT_ALL_ALERTS = (totalAlertsFormatted: string, totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.utilityBar.selectAllAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts }, + defaultMessage: + 'Select all {totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const CLEAR_SELECTION = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.clearSelectionTitle', + { + defaultMessage: 'Clear selection', + } +); + +export const BATCH_ACTIONS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActionsTitle', + { + defaultMessage: 'Batch actions', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_HOSTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInHostsTitle', + { + defaultMessage: 'View selected in hosts', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_NETWORK = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInNetworkTitle', + { + defaultMessage: 'View selected in network', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_TIMELINE = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInTimelineTitle', + { + defaultMessage: 'View selected in timeline', + } +); + +export const BATCH_ACTION_OPEN_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.openSelectedTitle', + { + defaultMessage: 'Open selected', + } +); + +export const BATCH_ACTION_CLOSE_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.closeSelectedTitle', + { + defaultMessage: 'Close selected', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.test.tsx similarity index 58% rename from x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.test.tsx index 71da68108da7e..b191464984c53 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.test.tsx @@ -9,23 +9,23 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; import { TimelineAction } from '../../../timelines/components/timeline/body/actions'; -import { buildSignalsRuleIdFilter, getSignalsActions } from './default_config'; +import { buildAlertsRuleIdFilter, getAlertActions } from './default_config'; import { CreateTimeline, SetEventsDeletedProps, SetEventsLoadingProps, UpdateTimelineLoading, } from './types'; -import { mockEcsDataWithSignal } from '../../../common/mock/mock_ecs'; -import { sendSignalToTimelineAction, updateSignalStatusAction } from './actions'; +import { mockEcsDataWithAlert } from '../../../common/mock/mock_ecs'; +import { sendAlertToTimelineAction, updateAlertStatusAction } from './actions'; import * as i18n from './translations'; jest.mock('./actions'); -describe('signals default_config', () => { - describe('buildSignalsRuleIdFilter', () => { +describe('alerts default_config', () => { + describe('buildAlertsRuleIdFilter', () => { test('given a rule id this will return an array with a single filter', () => { - const filters: Filter[] = buildSignalsRuleIdFilter('rule-id-1'); + const filters: Filter[] = buildAlertsRuleIdFilter('rule-id-1'); const expectedFilter: Filter = { meta: { alias: null, @@ -48,22 +48,27 @@ describe('signals default_config', () => { }); }); - describe('getSignalsActions', () => { + describe('getAlertActions', () => { let setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; let setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; let createTimeline: CreateTimeline; let updateTimelineIsLoading: UpdateTimelineLoading; + let onAlertStatusUpdateSuccess: (count: number, status: string) => void; + let onAlertStatusUpdateFailure: (status: string, error: Error) => void; + beforeEach(() => { setEventsLoading = jest.fn(); setEventsDeleted = jest.fn(); createTimeline = jest.fn(); updateTimelineIsLoading = jest.fn(); + onAlertStatusUpdateSuccess = jest.fn(); + onAlertStatusUpdateFailure = jest.fn(); }); describe('timeline tooltip', () => { - test('it invokes sendSignalToTimelineAction when button clicked', () => { - const signalsActions = getSignalsActions({ + test('it invokes sendAlertToTimelineAction when button clicked', () => { + const alertsActions = getAlertActions({ canUserCRUD: true, hasIndexWrite: true, setEventsLoading, @@ -71,25 +76,27 @@ describe('signals default_config', () => { createTimeline, status: 'open', updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); - const timelineAction = signalsActions[0].getAction({ + const timelineAction = alertsActions[0].getAction({ eventId: 'even-id', - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, }); const wrapper = mount(timelineAction as React.ReactElement); wrapper.find(EuiButtonIcon).simulate('click'); - expect(sendSignalToTimelineAction).toHaveBeenCalled(); + expect(sendAlertToTimelineAction).toHaveBeenCalled(); }); }); - describe('signal open action', () => { - let signalsActions: TimelineAction[]; - let signalOpenAction: JSX.Element; + describe('alert open action', () => { + let alertsActions: TimelineAction[]; + let alertOpenAction: JSX.Element; let wrapper: ReactWrapper; beforeEach(() => { - signalsActions = getSignalsActions({ + alertsActions = getAlertActions({ canUserCRUD: true, hasIndexWrite: true, setEventsLoading, @@ -97,53 +104,57 @@ describe('signals default_config', () => { createTimeline, status: 'open', updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); - signalOpenAction = signalsActions[1].getAction({ + alertOpenAction = alertsActions[1].getAction({ eventId: 'event-id', - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, }); - wrapper = mount(signalOpenAction as React.ReactElement); + wrapper = mount(alertOpenAction as React.ReactElement); }); afterEach(() => { wrapper.unmount(); }); - test('it invokes updateSignalStatusAction when button clicked', () => { + test('it invokes updateAlertStatusAction when button clicked', () => { wrapper.find(EuiButtonIcon).simulate('click'); - expect(updateSignalStatusAction).toHaveBeenCalledWith({ - signalIds: ['event-id'], + expect(updateAlertStatusAction).toHaveBeenCalledWith({ + alertIds: ['event-id'], status: 'open', setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); }); test('it displays expected text on hover', () => { - const openSignal = wrapper.find(EuiToolTip); - openSignal.simulate('mouseOver'); + const openAlert = wrapper.find(EuiToolTip); + openAlert.simulate('mouseOver'); const tooltip = wrapper.find('.euiToolTipPopover').text(); - expect(tooltip).toEqual(i18n.ACTION_OPEN_SIGNAL); + expect(tooltip).toEqual(i18n.ACTION_OPEN_ALERT); }); test('it displays expected icon', () => { const icon = wrapper.find(EuiButtonIcon).props().iconType; - expect(icon).toEqual('securitySignalDetected'); + expect(icon).toEqual('securityAlertDetected'); }); }); - describe('signal close action', () => { - let signalsActions: TimelineAction[]; - let signalCloseAction: JSX.Element; + describe('alert close action', () => { + let alertsActions: TimelineAction[]; + let alertCloseAction: JSX.Element; let wrapper: ReactWrapper; beforeEach(() => { - signalsActions = getSignalsActions({ + alertsActions = getAlertActions({ canUserCRUD: true, hasIndexWrite: true, setEventsLoading, @@ -151,42 +162,46 @@ describe('signals default_config', () => { createTimeline, status: 'closed', updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); - signalCloseAction = signalsActions[1].getAction({ + alertCloseAction = alertsActions[1].getAction({ eventId: 'event-id', - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, }); - wrapper = mount(signalCloseAction as React.ReactElement); + wrapper = mount(alertCloseAction as React.ReactElement); }); afterEach(() => { wrapper.unmount(); }); - test('it invokes updateSignalStatusAction when status button clicked', () => { + test('it invokes updateAlertStatusAction when status button clicked', () => { wrapper.find(EuiButtonIcon).simulate('click'); - expect(updateSignalStatusAction).toHaveBeenCalledWith({ - signalIds: ['event-id'], + expect(updateAlertStatusAction).toHaveBeenCalledWith({ + alertIds: ['event-id'], status: 'closed', setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); }); test('it displays expected text on hover', () => { - const closeSignal = wrapper.find(EuiToolTip); - closeSignal.simulate('mouseOver'); + const closeAlert = wrapper.find(EuiToolTip); + closeAlert.simulate('mouseOver'); const tooltip = wrapper.find('.euiToolTipPopover').text(); - expect(tooltip).toEqual(i18n.ACTION_CLOSE_SIGNAL); + expect(tooltip).toEqual(i18n.ACTION_CLOSE_ALERT); }); test('it displays expected icon', () => { const icon = wrapper.find(EuiButtonIcon).props().iconType; - expect(icon).toEqual('securitySignalResolved'); + expect(icon).toEqual('securityAlertResolved'); }); }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.tsx similarity index 77% rename from x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.tsx index 05e0baba66d0a..6cef2e7c53c46 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.tsx @@ -23,8 +23,8 @@ import { import { ColumnHeaderOptions, SubsetTimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { FILTER_OPEN } from './signals_filter_group'; -import { sendSignalToTimelineAction, updateSignalStatusAction } from './actions'; +import { FILTER_OPEN } from './alerts_filter_group'; +import { sendAlertToTimelineAction, updateAlertStatusAction } from './actions'; import * as i18n from './translations'; import { CreateTimeline, @@ -33,7 +33,7 @@ import { UpdateTimelineLoading, } from './types'; -export const signalsOpenFilters: Filter[] = [ +export const alertsOpenFilters: Filter[] = [ { meta: { alias: null, @@ -53,7 +53,7 @@ export const signalsOpenFilters: Filter[] = [ }, ]; -export const signalsClosedFilters: Filter[] = [ +export const alertsClosedFilters: Filter[] = [ { meta: { alias: null, @@ -73,7 +73,7 @@ export const signalsClosedFilters: Filter[] = [ }, ]; -export const buildSignalsRuleIdFilter = (ruleId: string): Filter[] => [ +export const buildAlertsRuleIdFilter = (ruleId: string): Filter[] => [ { meta: { alias: null, @@ -93,7 +93,7 @@ export const buildSignalsRuleIdFilter = (ruleId: string): Filter[] => [ }, ]; -export const signalsHeaders: ColumnHeaderOptions[] = [ +export const alertsHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: '@timestamp', @@ -102,32 +102,32 @@ export const signalsHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.name', - label: i18n.SIGNALS_HEADERS_RULE, + label: i18n.ALERTS_HEADERS_RULE, linkField: 'signal.rule.id', width: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.version', - label: i18n.SIGNALS_HEADERS_VERSION, + label: i18n.ALERTS_HEADERS_VERSION, width: 100, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.type', - label: i18n.SIGNALS_HEADERS_METHOD, + label: i18n.ALERTS_HEADERS_METHOD, width: 100, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.severity', - label: i18n.SIGNALS_HEADERS_SEVERITY, + label: i18n.ALERTS_HEADERS_SEVERITY, width: 105, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.risk_score', - label: i18n.SIGNALS_HEADERS_RISK_SCORE, + label: i18n.ALERTS_HEADERS_RISK_SCORE, width: 115, }, { @@ -171,9 +171,9 @@ export const signalsHeaders: ColumnHeaderOptions[] = [ }, ]; -export const signalsDefaultModel: SubsetTimelineModel = { +export const alertsDefaultModel: SubsetTimelineModel = { ...timelineDefaults, - columns: signalsHeaders, + columns: alertsHeaders, showCheckboxes: true, showRowRenderers: false, }; @@ -189,7 +189,7 @@ export const requiredFieldsForActions = [ 'signal.rule.id', ]; -export const getSignalsActions = ({ +export const getAlertActions = ({ apolloClient, canUserCRUD, hasIndexWrite, @@ -198,6 +198,8 @@ export const getSignalsActions = ({ createTimeline, status, updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }: { apolloClient?: ApolloClient<{}>; canUserCRUD: boolean; @@ -207,17 +209,19 @@ export const getSignalsActions = ({ createTimeline: CreateTimeline; status: 'open' | 'closed'; updateTimelineIsLoading: UpdateTimelineLoading; + onAlertStatusUpdateSuccess: (count: number, status: string) => void; + onAlertStatusUpdateFailure: (status: string, error: Error) => void; }): TimelineAction[] => [ { getAction: ({ ecsData }: TimelineActionProps): JSX.Element => ( - sendSignalToTimelineAction({ + sendAlertToTimelineAction({ apolloClient, createTimeline, ecsData, @@ -229,32 +233,34 @@ export const getSignalsActions = ({ /> ), - id: 'sendSignalToTimeline', + id: 'sendAlertToTimeline', width: 26, }, { getAction: ({ eventId }: TimelineActionProps): JSX.Element => ( - updateSignalStatusAction({ - signalIds: [eventId], + updateAlertStatusAction({ + alertIds: [eventId], status, setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }) } isDisabled={!canUserCRUD || !hasIndexWrite} - iconType={status === FILTER_OPEN ? 'securitySignalDetected' : 'securitySignalResolved'} + iconType={status === FILTER_OPEN ? 'securityAlertDetected' : 'securityAlertResolved'} aria-label="Next" /> ), - id: 'updateSignalStatus', + id: 'updateAlertStatus', width: 26, }, ]; diff --git a/x-pack/plugins/siem/public/alerts/components/signals/helpers.test.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.test.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/signals/helpers.test.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.test.ts diff --git a/x-pack/plugins/siem/public/alerts/components/signals/helpers.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.ts similarity index 98% rename from x-pack/plugins/siem/public/alerts/components/signals/helpers.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.ts index e9b8fdda84053..11a03b0426891 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/helpers.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.ts @@ -19,7 +19,7 @@ interface FindValueToChangeInQuery { /** * Fields that will be replaced with the template strings from a a saved timeline template. - * This is used for the signals detection engine feature when you save a timeline template + * This is used for the alerts detection engine feature when you save a timeline template * and are the fields you can replace when creating a template. */ const templateFields = [ diff --git a/x-pack/plugins/siem/public/alerts/components/signals/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.test.tsx similarity index 85% rename from x-pack/plugins/siem/public/alerts/components/signals/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/index.test.tsx index b66a9fc881045..51fdd828bcddb 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.test.tsx @@ -7,12 +7,12 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsTableComponent } from './index'; +import { AlertsTableComponent } from './index'; -describe('SignalsTableComponent', () => { +describe('AlertsTableComponent', () => { it('renders correctly', () => { const wrapper = shallow( - { /> ); - expect(wrapper.find('[title="Signals"]')).toBeTruthy(); + expect(wrapper.find('[title="Alerts"]')).toBeTruthy(); }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.tsx similarity index 73% rename from x-pack/plugins/siem/public/alerts/components/signals/index.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/index.tsx index eb19cfea97324..05c811d8e19bd 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.tsx @@ -22,32 +22,37 @@ import { TimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { useApolloClient } from '../../../common/utils/apollo_context'; -import { updateSignalStatusAction } from './actions'; +import { updateAlertStatusAction } from './actions'; import { - getSignalsActions, + getAlertActions, requiredFieldsForActions, - signalsClosedFilters, - signalsDefaultModel, - signalsOpenFilters, + alertsClosedFilters, + alertsDefaultModel, + alertsOpenFilters, } from './default_config'; import { FILTER_CLOSED, FILTER_OPEN, - SignalFilterOption, - SignalsTableFilterGroup, -} from './signals_filter_group'; -import { SignalsUtilityBar } from './signals_utility_bar'; + AlertFilterOption, + AlertsTableFilterGroup, +} from './alerts_filter_group'; +import { AlertsUtilityBar } from './alerts_utility_bar'; import * as i18n from './translations'; import { CreateTimelineProps, SetEventsDeletedProps, SetEventsLoadingProps, - UpdateSignalsStatusCallback, - UpdateSignalsStatusProps, + UpdateAlertsStatusCallback, + UpdateAlertsStatusProps, } from './types'; import { dispatchUpdateTimeline } from '../../../timelines/components/open_timeline/helpers'; +import { + useStateToaster, + displaySuccessToast, + displayErrorToast, +} from '../../../common/components/toasters'; -export const SIGNALS_PAGE_TIMELINE_ID = 'signals-page'; +export const ALERTS_TABLE_TIMELINE_ID = 'alerts-table'; interface OwnProps { canUserCRUD: boolean; @@ -59,9 +64,9 @@ interface OwnProps { to: number; } -type SignalsTableComponentProps = OwnProps & PropsFromRedux; +type AlertsTableComponentProps = OwnProps & PropsFromRedux; -export const SignalsTableComponent: React.FC = ({ +export const AlertsTableComponent: React.FC = ({ canUserCRUD, clearEventsDeleted, clearEventsLoading, @@ -86,11 +91,12 @@ export const SignalsTableComponent: React.FC = ({ const apolloClient = useApolloClient(); const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); - const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const [{ browserFields, indexPatterns }] = useFetchIndexPatterns( signalsIndex !== '' ? [signalsIndex] : [] ); const kibana = useKibana(); + const [, dispatchToaster] = useStateToaster(); const getGlobalQuery = useCallback(() => { if (browserFields != null && indexPatterns != null) { @@ -134,16 +140,37 @@ export const SignalsTableComponent: React.FC = ({ const setEventsLoadingCallback = useCallback( ({ eventIds, isLoading }: SetEventsLoadingProps) => { - setEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isLoading }); + setEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isLoading }); }, - [setEventsLoading, SIGNALS_PAGE_TIMELINE_ID] + [setEventsLoading, ALERTS_TABLE_TIMELINE_ID] ); const setEventsDeletedCallback = useCallback( ({ eventIds, isDeleted }: SetEventsDeletedProps) => { - setEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isDeleted }); + setEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isDeleted }); + }, + [setEventsDeleted, ALERTS_TABLE_TIMELINE_ID] + ); + + const onAlertStatusUpdateSuccess = useCallback( + (count: number, status: string) => { + const title = + status === 'closed' + ? i18n.CLOSED_ALERT_SUCCESS_TOAST(count) + : i18n.OPENED_ALERT_SUCCESS_TOAST(count); + + displaySuccessToast(title, dispatchToaster); + }, + [dispatchToaster] + ); + + const onAlertStatusUpdateFailure = useCallback( + (status: string, error: Error) => { + const title = + status === 'closed' ? i18n.CLOSED_ALERT_FAILED_TOAST : i18n.OPENED_ALERT_FAILED_TOAST; + displayErrorToast(title, [error.message], dispatchToaster); }, - [setEventsDeleted, SIGNALS_PAGE_TIMELINE_ID] + [dispatchToaster] ); // Catches state change isSelectAllChecked->false upon user selection change to reset utility bar @@ -157,10 +184,10 @@ export const SignalsTableComponent: React.FC = ({ // Callback for when open/closed filter changes const onFilterGroupChangedCallback = useCallback( - (newFilterGroup: SignalFilterOption) => { - clearEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID }); - clearEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID }); - clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); + (newFilterGroup: AlertFilterOption) => { + clearEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID }); + clearEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID }); + clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID }); setFilterGroup(newFilterGroup); }, [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup] @@ -168,7 +195,7 @@ export const SignalsTableComponent: React.FC = ({ // Callback for clearing entire selection from utility bar const clearSelectionCallback = useCallback(() => { - clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); + clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID }); setSelectAll(false); setShowClearSelectionAction(false); }, [clearSelected, setSelectAll, setShowClearSelectionAction]); @@ -181,14 +208,16 @@ export const SignalsTableComponent: React.FC = ({ setShowClearSelectionAction(true); }, [setSelectAll, setShowClearSelectionAction]); - const updateSignalsStatusCallback: UpdateSignalsStatusCallback = useCallback( - async (refetchQuery: inputsModel.Refetch, { signalIds, status }: UpdateSignalsStatusProps) => { - await updateSignalStatusAction({ + const updateAlertsStatusCallback: UpdateAlertsStatusCallback = useCallback( + async (refetchQuery: inputsModel.Refetch, { alertIds, status }: UpdateAlertsStatusProps) => { + await updateAlertStatusAction({ query: showClearSelectionAction ? getGlobalQuery()?.filterQuery : undefined, - signalIds: Object.keys(selectedEventIds), + alertIds: Object.keys(selectedEventIds), status, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); refetchQuery(); }, @@ -198,14 +227,16 @@ export const SignalsTableComponent: React.FC = ({ setEventsDeletedCallback, setEventsLoadingCallback, showClearSelectionAction, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, ] ); - // Callback for creating the SignalUtilityBar which receives totalCount from EventsViewer component + // Callback for creating the AlertsUtilityBar which receives totalCount from EventsViewer component const utilityBarCallback = useCallback( (refetchQuery: inputsModel.Refetch, totalCount: number) => { return ( - 0} clearSelection={clearSelectionCallback} @@ -215,7 +246,7 @@ export const SignalsTableComponent: React.FC = ({ selectedEventIds={selectedEventIds} showClearSelection={showClearSelectionAction} totalCount={totalCount} - updateSignalsStatus={updateSignalsStatusCallback.bind(null, refetchQuery)} + updateAlertsStatus={updateAlertsStatusCallback.bind(null, refetchQuery)} /> ); }, @@ -228,14 +259,14 @@ export const SignalsTableComponent: React.FC = ({ selectAllCallback, selectedEventIds, showClearSelectionAction, - updateSignalsStatusCallback, + updateAlertsStatusCallback, ] ); - // Send to Timeline / Update Signal Status Actions for each table row + // Send to Timeline / Update Alert Status Actions for each table row const additionalActions = useMemo( () => - getSignalsActions({ + getAlertActions({ apolloClient, canUserCRUD, hasIndexWrite, @@ -244,6 +275,8 @@ export const SignalsTableComponent: React.FC = ({ setEventsDeleted: setEventsDeletedCallback, status: filterGroup === FILTER_OPEN ? FILTER_CLOSED : FILTER_OPEN, updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }), [ apolloClient, @@ -254,44 +287,46 @@ export const SignalsTableComponent: React.FC = ({ setEventsLoadingCallback, setEventsDeletedCallback, updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, ] ); const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); const defaultFiltersMemo = useMemo(() => { if (isEmpty(defaultFilters)) { - return filterGroup === FILTER_OPEN ? signalsOpenFilters : signalsClosedFilters; + return filterGroup === FILTER_OPEN ? alertsOpenFilters : alertsClosedFilters; } else if (defaultFilters != null && !isEmpty(defaultFilters)) { return [ ...defaultFilters, - ...(filterGroup === FILTER_OPEN ? signalsOpenFilters : signalsClosedFilters), + ...(filterGroup === FILTER_OPEN ? alertsOpenFilters : alertsClosedFilters), ]; } }, [defaultFilters, filterGroup]); const timelineTypeContext = useMemo( () => ({ - documentType: i18n.SIGNALS_DOCUMENT_TYPE, - footerText: i18n.TOTAL_COUNT_OF_SIGNALS, - loadingText: i18n.LOADING_SIGNALS, + documentType: i18n.ALERTS_DOCUMENT_TYPE, + footerText: i18n.TOTAL_COUNT_OF_ALERTS, + loadingText: i18n.LOADING_ALERTS, queryFields: requiredFieldsForActions, timelineActions: additionalActions, - title: i18n.SIGNALS_TABLE_TITLE, + title: i18n.ALERTS_TABLE_TITLE, selectAll: canUserCRUD ? selectAll : false, }), [additionalActions, canUserCRUD, selectAll] ); const headerFilterGroup = useMemo( - () => , + () => , [onFilterGroupChangedCallback] ); if (loading || isEmpty(signalsIndex)) { return ( - - + + ); } @@ -300,10 +335,10 @@ export const SignalsTableComponent: React.FC = ({ { const getGlobalInputs = inputsSelectors.globalSelector(); const mapStateToProps = (state: State) => { const timeline: TimelineModel = - getTimeline(state, SIGNALS_PAGE_TIMELINE_ID) ?? timelineDefaults; + getTimeline(state, ALERTS_TABLE_TIMELINE_ID) ?? timelineDefaults; const { deletedEventIds, isSelectAllChecked, loadingEventIds, selectedEventIds } = timeline; const globalInputs: inputsModel.InputsRange = getGlobalInputs(state); @@ -366,4 +401,4 @@ const connector = connect(makeMapStateToProps, mapDispatchToProps); type PropsFromRedux = ConnectedProps; -export const SignalsTable = connector(React.memo(SignalsTableComponent)); +export const AlertsTable = connector(React.memo(AlertsTableComponent)); diff --git a/x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts new file mode 100644 index 0000000000000..4f34e9d031eed --- /dev/null +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', { + defaultMessage: 'Detection engine', +}); + +export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.alerts.tableTitle', { + defaultMessage: 'Alert list', +}); + +export const ALERTS_DOCUMENT_TYPE = i18n.translate( + 'xpack.siem.detectionEngine.alerts.documentTypeTitle', + { + defaultMessage: 'Alerts', + } +); + +export const OPEN_ALERTS = i18n.translate('xpack.siem.detectionEngine.alerts.openAlertsTitle', { + defaultMessage: 'Open alerts', +}); + +export const CLOSED_ALERTS = i18n.translate('xpack.siem.detectionEngine.alerts.closedAlertsTitle', { + defaultMessage: 'Closed alerts', +}); + +export const LOADING_ALERTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.loadingAlertsTitle', + { + defaultMessage: 'Loading Alerts', + } +); + +export const TOTAL_COUNT_OF_ALERTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.totalCountOfAlertsTitle', + { + defaultMessage: 'alerts match the search criteria', + } +); + +export const ALERTS_HEADERS_RULE = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.ruleTitle', + { + defaultMessage: 'Rule', + } +); + +export const ALERTS_HEADERS_VERSION = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.versionTitle', + { + defaultMessage: 'Version', + } +); + +export const ALERTS_HEADERS_METHOD = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.methodTitle', + { + defaultMessage: 'Method', + } +); + +export const ALERTS_HEADERS_SEVERITY = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.severityTitle', + { + defaultMessage: 'Severity', + } +); + +export const ALERTS_HEADERS_RISK_SCORE = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.riskScoreTitle', + { + defaultMessage: 'Risk Score', + } +); + +export const ACTION_OPEN_ALERT = i18n.translate( + 'xpack.siem.detectionEngine.alerts.actions.openAlertTitle', + { + defaultMessage: 'Open alert', + } +); + +export const ACTION_CLOSE_ALERT = i18n.translate( + 'xpack.siem.detectionEngine.alerts.actions.closeAlertTitle', + { + defaultMessage: 'Close alert', + } +); + +export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate( + 'xpack.siem.detectionEngine.alerts.actions.investigateInTimelineTitle', + { + defaultMessage: 'Investigate in timeline', + } +); + +export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.closedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.openedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully opened {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const CLOSED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.siem.detectionEngine.alerts.closedAlertFailedToastMessage', + { + defaultMessage: 'Failed to close alert(s).', + } +); + +export const OPENED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.siem.detectionEngine.alerts.openedAlertFailedToastMessage', + { + defaultMessage: 'Failed to open alert(s)', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/types.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/types.ts similarity index 72% rename from x-pack/plugins/siem/public/alerts/components/signals/types.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_table/types.ts index b3c770415ed57..ba342ae441857 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/types.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/types.ts @@ -20,28 +20,28 @@ export interface SetEventsDeletedProps { isDeleted: boolean; } -export interface UpdateSignalsStatusProps { - signalIds: string[]; +export interface UpdateAlertsStatusProps { + alertIds: string[]; status: 'open' | 'closed'; } -export type UpdateSignalsStatusCallback = ( +export type UpdateAlertsStatusCallback = ( refetchQuery: inputsModel.Refetch, - { signalIds, status }: UpdateSignalsStatusProps + { alertIds, status }: UpdateAlertsStatusProps ) => void; -export type UpdateSignalsStatus = ({ signalIds, status }: UpdateSignalsStatusProps) => void; +export type UpdateAlertsStatus = ({ alertIds, status }: UpdateAlertsStatusProps) => void; -export interface UpdateSignalStatusActionProps { +export interface UpdateAlertStatusActionProps { query?: string; - signalIds: string[]; + alertIds: string[]; status: 'open' | 'closed'; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; + onAlertStatusUpdateSuccess: (count: number, status: string) => void; + onAlertStatusUpdateFailure: (status: string, error: Error) => void; } -export type SendSignalsToTimeline = () => void; - -export interface SendSignalToTimelineActionProps { +export interface SendAlertToTimelineActionProps { apolloClient?: ApolloClient<{}>; createTimeline: CreateTimeline; ecsData: Ecs; diff --git a/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts b/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts index 303431a559e8f..651faf0b17318 100644 --- a/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts @@ -17,6 +17,6 @@ export const PAGE_BADGE_TOOLTIP = i18n.translate( 'xpack.siem.detectionEngine.headerPage.pageBadgeTooltip', { defaultMessage: - 'Detections is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.', + 'Alerts is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.', } ); diff --git a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.test.tsx similarity index 72% rename from x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.test.tsx index 2e6890e60fc61..2b1173f8a4843 100644 --- a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.test.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { NoWriteSignalsCallOut } from './index'; +import { NoWriteAlertsCallOut } from './index'; -describe('no_write_signals_callout', () => { +describe('no_write_alerts_callout', () => { it('renders correctly', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('EuiCallOut')).toBeTruthy(); }); diff --git a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.tsx b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.tsx similarity index 72% rename from x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.tsx rename to x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.tsx index 1950531998450..dcb50ef43a841 100644 --- a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.tsx @@ -9,13 +9,13 @@ import React, { memo, useCallback, useState } from 'react'; import * as i18n from './translations'; -const NoWriteSignalsCallOutComponent = () => { +const NoWriteAlertsCallOutComponent = () => { const [showCallOut, setShowCallOut] = useState(true); const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]); return showCallOut ? ( - -

{i18n.NO_WRITE_SIGNALS_CALLOUT_MSG}

+ +

{i18n.NO_WRITE_ALERTS_CALLOUT_MSG}

{i18n.DISMISS_CALLOUT} @@ -23,4 +23,4 @@ const NoWriteSignalsCallOutComponent = () => { ) : null; }; -export const NoWriteSignalsCallOut = memo(NoWriteSignalsCallOutComponent); +export const NoWriteAlertsCallOut = memo(NoWriteAlertsCallOutComponent); diff --git a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/translations.ts b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/translations.ts similarity index 52% rename from x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/translations.ts rename to x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/translations.ts index 065d775e1dc6a..f79ede56ef9ae 100644 --- a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/translations.ts @@ -6,23 +6,23 @@ import { i18n } from '@kbn/i18n'; -export const NO_WRITE_SIGNALS_CALLOUT_TITLE = i18n.translate( - 'xpack.siem.detectionEngine.noWriteSignalsCallOutTitle', +export const NO_WRITE_ALERTS_CALLOUT_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.noWriteAlertsCallOutTitle', { - defaultMessage: 'Signals index permissions required', + defaultMessage: 'Alerts index permissions required', } ); -export const NO_WRITE_SIGNALS_CALLOUT_MSG = i18n.translate( - 'xpack.siem.detectionEngine.noWriteSignalsCallOutMsg', +export const NO_WRITE_ALERTS_CALLOUT_MSG = i18n.translate( + 'xpack.siem.detectionEngine.noWriteAlertsCallOutMsg', { defaultMessage: - 'You are currently missing the required permissions to update signals. Please contact your administrator for further assistance.', + 'You are currently missing the required permissions to update alerts. Please contact your administrator for further assistance.', } ); export const DISMISS_CALLOUT = i18n.translate( - 'xpack.siem.detectionEngine.dismissNoWriteSignalButton', + 'xpack.siem.detectionEngine.dismissNoWriteAlertButton', { defaultMessage: 'Dismiss', } diff --git a/x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx b/x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx index be96ab10bd2b5..43416abe6e288 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/description_step/actions_description.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { startCase } from 'lodash/fp'; -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; const ActionsDescription = ({ actions }: { actions: AlertAction[] }) => { if (!actions.length) return null; diff --git a/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts b/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts index 407dedbf27baf..9b36d96cef9ca 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts @@ -17,7 +17,7 @@ export const PRE_BUILT_MSG = i18n.translate( 'xpack.siem.detectionEngine.rules.prePackagedRules.emptyPromptMessage', { defaultMessage: - 'Elastic SIEM comes with prebuilt detection rules that run in the background and create signals when their conditions are met. By default, all prebuilt rules are disabled and you select which rules you want to activate.', + 'Elastic SIEM comes with prebuilt detection rules that run in the background and create alerts when their conditions are met. By default, all prebuilt rules are disabled and you select which rules you want to activate.', } ); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx index d8064eb4ad391..b77de683d5f20 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx @@ -18,7 +18,7 @@ import { ActionType, loadActionTypes, } from '../../../../../../triggers_actions_ui/public'; -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { useKibana } from '../../../../common/lib/kibana'; import { FORM_ERRORS_TITLE } from './translations'; @@ -26,7 +26,7 @@ type ThrottleSelectField = typeof SelectField; const DEFAULT_ACTION_GROUP_ID = 'default'; const DEFAULT_ACTION_MESSAGE = - 'Rule {{context.rule.name}} generated {{state.signals_count}} signals'; + 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts'; const FieldErrorsContainer = styled.div` p { diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx index 33aa02adf3f10..69e8ed10d1b34 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx @@ -183,7 +183,7 @@ export const schema: FormSchema = { }), helpText: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.guideHelpText', { defaultMessage: - 'Provide helpful information for analysts that are performing a signal investigation. This guide will appear on both the rule details page and in timelines created from signals generated by this rule.', + 'Provide helpful information for analysts that are performing an alert investigation. This guide will appear on both the rule details page and in timelines created from alerts generated by this rule.', }), labelAppend: OptionalFieldLabel, }, diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx index 14afa63ef3609..0c309c8c51a15 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx @@ -168,7 +168,7 @@ export const schema: FormSchema = { helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateHelpText', { - defaultMessage: 'Select which timeline to use when investigating generated signals.', + defaultMessage: 'Select which timeline to use when investigating generated alerts.', } ), }, diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx index 99ff8a6727372..d010a3128b24d 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx @@ -22,8 +22,7 @@ export const schema: FormSchema = { helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalHelpText', { - defaultMessage: - 'Rules run periodically and detect signals within the specified time frame.', + defaultMessage: 'Rules run periodically and detect alerts within the specified time frame.', } ), }, @@ -38,7 +37,7 @@ export const schema: FormSchema = { helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText', { - defaultMessage: 'Adds time to the look-back period to prevent missed signals.', + defaultMessage: 'Adds time to the look-back period to prevent missed alerts.', } ), }, diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts b/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts deleted file mode 100644 index b876177d5e4d1..0000000000000 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const SHOWING_SIGNALS = (totalSignalsFormatted: string, totalSignals: number) => - i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.showingSignalsTitle', { - values: { totalSignalsFormatted, totalSignals }, - defaultMessage: - 'Showing {totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', - }); - -export const SELECTED_SIGNALS = (selectedSignalsFormatted: string, selectedSignals: number) => - i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.selectedSignalsTitle', { - values: { selectedSignalsFormatted, selectedSignals }, - defaultMessage: - 'Selected {selectedSignalsFormatted} {selectedSignals, plural, =1 {signal} other {signals}}', - }); - -export const SELECT_ALL_SIGNALS = (totalSignalsFormatted: string, totalSignals: number) => - i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.selectAllSignalsTitle', { - values: { totalSignalsFormatted, totalSignals }, - defaultMessage: - 'Select all {totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', - }); - -export const CLEAR_SELECTION = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.clearSelectionTitle', - { - defaultMessage: 'Clear selection', - } -); - -export const BATCH_ACTIONS = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActionsTitle', - { - defaultMessage: 'Batch actions', - } -); - -export const BATCH_ACTION_VIEW_SELECTED_IN_HOSTS = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInHostsTitle', - { - defaultMessage: 'View selected in hosts', - } -); - -export const BATCH_ACTION_VIEW_SELECTED_IN_NETWORK = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInNetworkTitle', - { - defaultMessage: 'View selected in network', - } -); - -export const BATCH_ACTION_VIEW_SELECTED_IN_TIMELINE = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInTimelineTitle', - { - defaultMessage: 'View selected in timeline', - } -); - -export const BATCH_ACTION_OPEN_SELECTED = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.openSelectedTitle', - { - defaultMessage: 'Open selected', - } -); - -export const BATCH_ACTION_CLOSE_SELECTED = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.closeSelectedTitle', - { - defaultMessage: 'Close selected', - } -); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/translations.ts b/x-pack/plugins/siem/public/alerts/components/signals/translations.ts deleted file mode 100644 index f68dcd932bc32..0000000000000 --- a/x-pack/plugins/siem/public/alerts/components/signals/translations.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', { - defaultMessage: 'Detection engine', -}); - -export const SIGNALS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.signals.tableTitle', { - defaultMessage: 'Signals', -}); - -export const SIGNALS_DOCUMENT_TYPE = i18n.translate( - 'xpack.siem.detectionEngine.signals.documentTypeTitle', - { - defaultMessage: 'Signals', - } -); - -export const OPEN_SIGNALS = i18n.translate('xpack.siem.detectionEngine.signals.openSignalsTitle', { - defaultMessage: 'Open signals', -}); - -export const CLOSED_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.closedSignalsTitle', - { - defaultMessage: 'Closed signals', - } -); - -export const LOADING_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.loadingSignalsTitle', - { - defaultMessage: 'Loading Signals', - } -); - -export const TOTAL_COUNT_OF_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.totalCountOfSignalsTitle', - { - defaultMessage: 'signals match the search criteria', - } -); - -export const SIGNALS_HEADERS_RULE = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.ruleTitle', - { - defaultMessage: 'Rule', - } -); - -export const SIGNALS_HEADERS_VERSION = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.versionTitle', - { - defaultMessage: 'Version', - } -); - -export const SIGNALS_HEADERS_METHOD = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.methodTitle', - { - defaultMessage: 'Method', - } -); - -export const SIGNALS_HEADERS_SEVERITY = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.severityTitle', - { - defaultMessage: 'Severity', - } -); - -export const SIGNALS_HEADERS_RISK_SCORE = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.riskScoreTitle', - { - defaultMessage: 'Risk Score', - } -); - -export const ACTION_OPEN_SIGNAL = i18n.translate( - 'xpack.siem.detectionEngine.signals.actions.openSignalTitle', - { - defaultMessage: 'Open signal', - } -); - -export const ACTION_CLOSE_SIGNAL = i18n.translate( - 'xpack.siem.detectionEngine.signals.actions.closeSignalTitle', - { - defaultMessage: 'Close signal', - } -); - -export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate( - 'xpack.siem.detectionEngine.signals.actions.investigateInTimelineTitle', - { - defaultMessage: 'Investigate in timeline', - } -); diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx b/x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx deleted file mode 100644 index b1d7f2cfe7eb5..0000000000000 --- a/x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiLoadingSpinner } from '@elastic/eui'; -import { FormattedRelative } from '@kbn/i18n/react'; -import React, { useState, useEffect } from 'react'; - -import { useQuerySignals } from '../../containers/detection_engine/signals/use_query'; -import { buildLastSignalsQuery } from './query.dsl'; -import { Aggs } from './types'; - -interface SignalInfo { - ruleId?: string | null; -} - -type Return = [React.ReactNode, React.ReactNode]; - -export const useSignalInfo = ({ ruleId = null }: SignalInfo): Return => { - const [lastSignals, setLastSignals] = useState( - - ); - const [totalSignals, setTotalSignals] = useState( - - ); - - const { loading, data: signals } = useQuerySignals(buildLastSignalsQuery(ruleId)); - - useEffect(() => { - if (signals != null) { - const mySignals = signals; - setLastSignals( - mySignals.aggregations?.lastSeen.value != null ? ( - - ) : null - ); - setTotalSignals(<>{mySignals.hits.total.value}); - } else { - setLastSignals(null); - setTotalSignals(null); - } - }, [loading, signals]); - - return [lastSignals, totalSignals]; -}; diff --git a/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx index 81b2c4347e17c..b01edac2605eb 100644 --- a/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx @@ -7,11 +7,11 @@ import { renderHook } from '@testing-library/react-hooks'; import { useUserInfo } from './index'; -import { usePrivilegeUser } from '../../containers/detection_engine/signals/use_privilege_user'; -import { useSignalIndex } from '../../containers/detection_engine/signals/use_signal_index'; +import { usePrivilegeUser } from '../../containers/detection_engine/alerts/use_privilege_user'; +import { useSignalIndex } from '../../containers/detection_engine/alerts/use_signal_index'; import { useKibana } from '../../../common/lib/kibana'; -jest.mock('../../containers/detection_engine/signals/use_privilege_user'); -jest.mock('../../containers/detection_engine/signals/use_signal_index'); +jest.mock('../../containers/detection_engine/alerts/use_privilege_user'); +jest.mock('../../containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../common/lib/kibana'); describe('useUserInfo', () => { diff --git a/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx b/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx index faf9016292559..049463d4066d8 100644 --- a/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx @@ -7,8 +7,8 @@ import { noop } from 'lodash/fp'; import React, { useEffect, useReducer, Dispatch, createContext, useContext } from 'react'; -import { usePrivilegeUser } from '../../containers/detection_engine/signals/use_privilege_user'; -import { useSignalIndex } from '../../containers/detection_engine/signals/use_signal_index'; +import { usePrivilegeUser } from '../../containers/detection_engine/alerts/use_privilege_user'; +import { useSignalIndex } from '../../containers/detection_engine/alerts/use_signal_index'; import { useKibana } from '../../../common/lib/kibana'; export interface State { diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/__mocks__/api.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts similarity index 56% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/__mocks__/api.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts index 7cb1d7d574cf8..64a55a8ec6eba 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/__mocks__/api.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts @@ -4,26 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - QuerySignals, - SignalSearchResponse, - BasicSignals, - SignalsIndex, - Privilege, -} from '../types'; -import { signalsMock, mockSignalIndex, mockUserPrivilege } from '../mock'; +import { QueryAlerts, AlertSearchResponse, BasicSignals, AlertsIndex, Privilege } from '../types'; +import { alertsMock, mockSignalIndex, mockUserPrivilege } from '../mock'; -export const fetchQuerySignals = async ({ +export const fetchQueryAlerts = async ({ query, signal, -}: QuerySignals): Promise> => - Promise.resolve(signalsMock as SignalSearchResponse); +}: QueryAlerts): Promise> => + Promise.resolve(alertsMock as AlertSearchResponse); -export const getSignalIndex = async ({ signal }: BasicSignals): Promise => +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => Promise.resolve(mockSignalIndex); export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => Promise.resolve(mockUserPrivilege); -export const createSignalIndex = async ({ signal }: BasicSignals): Promise => +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => Promise.resolve(mockSignalIndex); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.test.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.test.ts similarity index 68% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.test.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.test.ts index 67d81d19faa7c..3cd819b55685c 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.test.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.test.ts @@ -6,15 +6,15 @@ import { KibanaServices } from '../../../../common/lib/kibana'; import { - signalsMock, - mockSignalsQuery, - mockStatusSignalQuery, + alertsMock, + mockAlertsQuery, + mockStatusAlertQuery, mockSignalIndex, mockUserPrivilege, } from './mock'; import { - fetchQuerySignals, - updateSignalStatus, + fetchQueryAlerts, + updateAlertStatus, getSignalIndex, getUserPrivilege, createSignalIndex, @@ -27,41 +27,41 @@ jest.mock('../../../../common/lib/kibana'); const fetchMock = jest.fn(); mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } }); -describe('Detections Signals API', () => { - describe('fetchQuerySignals', () => { +describe('Detections Alerts API', () => { + describe('fetchQueryAlerts', () => { beforeEach(() => { fetchMock.mockClear(); - fetchMock.mockResolvedValue(signalsMock); + fetchMock.mockResolvedValue(alertsMock); }); test('check parameter url, body', async () => { - await fetchQuerySignals({ query: mockSignalsQuery, signal: abortCtrl.signal }); + await fetchQueryAlerts({ query: mockAlertsQuery, signal: abortCtrl.signal }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/search', { body: - '{"aggs":{"signalsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"signals":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', + '{"aggs":{"alertsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"alerts":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', method: 'POST', signal: abortCtrl.signal, }); }); test('happy path', async () => { - const signalsResp = await fetchQuerySignals({ - query: mockSignalsQuery, + const signalsResp = await fetchQueryAlerts({ + query: mockAlertsQuery, signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(signalsMock); + expect(signalsResp).toEqual(alertsMock); }); }); - describe('updateSignalStatus', () => { + describe('updateAlertStatus', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue({}); }); - test('check parameter url, body when closing a signal', async () => { - await updateSignalStatus({ - query: mockStatusSignalQuery, + test('check parameter url, body when closing an alert', async () => { + await updateAlertStatus({ + query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'closed', }); @@ -73,9 +73,9 @@ describe('Detections Signals API', () => { }); }); - test('check parameter url, body when opening a signal', async () => { - await updateSignalStatus({ - query: mockStatusSignalQuery, + test('check parameter url, body when opening an alert', async () => { + await updateAlertStatus({ + query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'open', }); @@ -88,12 +88,12 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await updateSignalStatus({ - query: mockStatusSignalQuery, + const alertsResp = await updateAlertStatus({ + query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'open', }); - expect(signalsResp).toEqual({}); + expect(alertsResp).toEqual({}); }); }); @@ -112,10 +112,10 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await getSignalIndex({ + const alertsResp = await getSignalIndex({ signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(mockSignalIndex); + expect(alertsResp).toEqual(mockSignalIndex); }); }); @@ -134,10 +134,10 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await getUserPrivilege({ + const alertsResp = await getUserPrivilege({ signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(mockUserPrivilege); + expect(alertsResp).toEqual(mockUserPrivilege); }); }); @@ -156,10 +156,10 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await createSignalIndex({ + const alertsResp = await createSignalIndex({ signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(mockSignalIndex); + expect(alertsResp).toEqual(mockSignalIndex); }); }); }); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.ts similarity index 72% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.ts index 860305dd58e67..ccf35c9671836 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { UpdateDocumentByQueryResponse } from 'elasticsearch'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, @@ -14,25 +15,25 @@ import { KibanaServices } from '../../../../common/lib/kibana'; import { BasicSignals, Privilege, - QuerySignals, - SignalSearchResponse, - SignalsIndex, - UpdateSignalStatusProps, + QueryAlerts, + AlertSearchResponse, + AlertsIndex, + UpdateAlertStatusProps, } from './types'; /** - * Fetch Signals by providing a query + * Fetch Alerts by providing a query * * @param query String to match a dsl * @param signal to cancel request * * @throws An error if response is not OK */ -export const fetchQuerySignals = async ({ +export const fetchQueryAlerts = async ({ query, signal, -}: QuerySignals): Promise> => - KibanaServices.get().http.fetch>( +}: QueryAlerts): Promise> => + KibanaServices.get().http.fetch>( DETECTION_ENGINE_QUERY_SIGNALS_URL, { method: 'POST', @@ -42,19 +43,19 @@ export const fetchQuerySignals = async ({ ); /** - * Update signal status by query + * Update alert status by query * - * @param query of signals to update + * @param query of alerts to update * @param status to update to('open' / 'closed') * @param signal AbortSignal for cancelling request * * @throws An error if response is not OK */ -export const updateSignalStatus = async ({ +export const updateAlertStatus = async ({ query, status, signal, -}: UpdateSignalStatusProps): Promise => +}: UpdateAlertStatusProps): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { method: 'POST', body: JSON.stringify({ status, ...query }), @@ -68,8 +69,8 @@ export const updateSignalStatus = async ({ * * @throws An error if response is not OK */ -export const getSignalIndex = async ({ signal }: BasicSignals): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { method: 'GET', signal, }); @@ -94,8 +95,8 @@ export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { method: 'POST', signal, }); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/mock.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/mock.ts similarity index 98% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/mock.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/mock.ts index 6b0c7e0078268..cd2cc1fe390ba 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/mock.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/mock.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SignalSearchResponse, SignalsIndex, Privilege } from './types'; +import { AlertSearchResponse, AlertsIndex, Privilege } from './types'; -export const signalsMock: SignalSearchResponse = { +export const alertsMock: AlertSearchResponse = { took: 7, timeout: false, _shards: { @@ -902,14 +902,14 @@ export const signalsMock: SignalSearchResponse = { ], }, aggregations: { - signalsByGrouping: { + alertsByGrouping: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ { key: '4', doc_count: 12600, - signals: { + alerts: { buckets: [ { key_as_string: '2020-01-21T04:30:00.000Z', @@ -939,9 +939,9 @@ export const signalsMock: SignalSearchResponse = { }, }; -export const mockSignalsQuery: object = { +export const mockAlertsQuery: object = { aggs: { - signalsByGrouping: { + alertsByGrouping: { terms: { field: 'signal.rule.risk_score', missing: 'All others', @@ -949,7 +949,7 @@ export const mockSignalsQuery: object = { size: 10, }, aggs: { - signals: { + alerts: { date_histogram: { field: '@timestamp', fixed_interval: '81000000ms', @@ -970,7 +970,7 @@ export const mockSignalsQuery: object = { }, }; -export const mockStatusSignalQuery: object = { +export const mockStatusAlertQuery: object = { bool: { filter: { terms: { _id: ['b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5'] }, @@ -978,7 +978,7 @@ export const mockStatusSignalQuery: object = { }, }; -export const mockSignalIndex: SignalsIndex = { +export const mockSignalIndex: AlertsIndex = { name: 'mock-signal-index', }; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/translations.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/translations.ts similarity index 55% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/translations.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/translations.ts index 2b8f54e5438df..2f3ebccdb14cd 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/translations.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/translations.ts @@ -6,29 +6,29 @@ import { i18n } from '@kbn/i18n'; -export const SIGNAL_FETCH_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription', +export const ALERT_FETCH_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.alerts.errorFetchingAlertsDescription', { - defaultMessage: 'Failed to query signals', + defaultMessage: 'Failed to query alerts', } ); export const PRIVILEGE_FETCH_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription', + 'xpack.siem.containers.detectionEngine.alerts.errorFetchingAlertsDescription', { - defaultMessage: 'Failed to query signals', + defaultMessage: 'Failed to query alerts', } ); export const SIGNAL_GET_NAME_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription', + 'xpack.siem.containers.detectionEngine.alerts.errorGetAlertDescription', { defaultMessage: 'Failed to get signal index name', } ); export const SIGNAL_POST_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription', + 'xpack.siem.containers.detectionEngine.alerts.errorPostAlertDescription', { defaultMessage: 'Failed to create signal index', } diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/types.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/types.ts similarity index 88% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/types.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/types.ts index 4e97c597546a7..b425cfd54a7fd 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/types.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/types.ts @@ -7,17 +7,17 @@ export interface BasicSignals { signal: AbortSignal; } -export interface QuerySignals extends BasicSignals { +export interface QueryAlerts extends BasicSignals { query: object; } -export interface SignalsResponse { +export interface AlertsResponse { took: number; timeout: boolean; } -export interface SignalSearchResponse - extends SignalsResponse { +export interface AlertSearchResponse + extends AlertsResponse { _shards: { total: number; successful: number; @@ -34,13 +34,13 @@ export interface SignalSearchResponse }; } -export interface UpdateSignalStatusProps { +export interface UpdateAlertStatusProps { query: object; status: 'open' | 'closed'; signal?: AbortSignal; // TODO: implement cancelling } -export interface SignalsIndex { +export interface AlertsIndex { name: string; } diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.test.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.test.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.test.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.test.tsx similarity index 61% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.test.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.test.tsx index c577f291f037e..8627b953c8dac 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.test.tsx +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.test.tsx @@ -5,13 +5,13 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { useQuerySignals, ReturnQuerySignals } from './use_query'; +import { useQueryAlerts, ReturnQueryAlerts } from './use_query'; import * as api from './api'; -import { mockSignalsQuery, signalsMock } from './mock'; +import { mockAlertsQuery, alertsMock } from './mock'; jest.mock('./api'); -describe('useQuerySignals', () => { +describe('useQueryAlerts', () => { const indexName = 'mock-index-name'; beforeEach(() => { jest.resetAllMocks(); @@ -20,8 +20,8 @@ describe('useQuerySignals', () => { await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >(() => useQuerySignals(mockSignalsQuery, indexName)); + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); await waitForNextUpdate(); expect(result.current).toEqual({ loading: true, @@ -34,72 +34,72 @@ describe('useQuerySignals', () => { }); }); - test('fetch signals data', async () => { + test('fetch alerts data', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >(() => useQuerySignals(mockSignalsQuery, indexName)); + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); await waitForNextUpdate(); await waitForNextUpdate(); expect(result.current).toEqual({ loading: false, - data: signalsMock, - response: JSON.stringify(signalsMock, null, 2), - request: JSON.stringify({ index: [indexName] ?? [''], body: mockSignalsQuery }, null, 2), + data: alertsMock, + response: JSON.stringify(alertsMock, null, 2), + request: JSON.stringify({ index: [indexName] ?? [''], body: mockAlertsQuery }, null, 2), setQuery: result.current.setQuery, refetch: result.current.refetch, }); }); }); - test('re-fetch signals data', async () => { - const spyOnfetchQuerySignals = jest.spyOn(api, 'fetchQuerySignals'); + test('re-fetch alerts data', async () => { + const spyOnfetchQueryAlerts = jest.spyOn(api, 'fetchQueryAlerts'); await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >(() => useQuerySignals(mockSignalsQuery, indexName)); + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); await waitForNextUpdate(); await waitForNextUpdate(); if (result.current.refetch) { result.current.refetch(); } await waitForNextUpdate(); - expect(spyOnfetchQuerySignals).toHaveBeenCalledTimes(2); + expect(spyOnfetchQueryAlerts).toHaveBeenCalledTimes(2); }); }); - test('fetch signal when index name changed', async () => { - const spyOnfetchRules = jest.spyOn(api, 'fetchQuerySignals'); + test('fetch alert when index name changed', async () => { + const spyOnfetchRules = jest.spyOn(api, 'fetchQueryAlerts'); await act(async () => { const { rerender, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >((args) => useQuerySignals(args[0], args[1]), { - initialProps: [mockSignalsQuery, indexName], + ReturnQueryAlerts + >((args) => useQueryAlerts(args[0], args[1]), { + initialProps: [mockAlertsQuery, indexName], }); await waitForNextUpdate(); await waitForNextUpdate(); - rerender([mockSignalsQuery, 'new-mock-index-name']); + rerender([mockAlertsQuery, 'new-mock-index-name']); await waitForNextUpdate(); expect(spyOnfetchRules).toHaveBeenCalledTimes(2); }); }); - test('fetch signal when query object changed', async () => { - const spyOnfetchRules = jest.spyOn(api, 'fetchQuerySignals'); + test('fetch alert when query object changed', async () => { + const spyOnfetchRules = jest.spyOn(api, 'fetchQueryAlerts'); await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >((args) => useQuerySignals(args[0], args[1]), { - initialProps: [mockSignalsQuery, indexName], + ReturnQueryAlerts + >((args) => useQueryAlerts(args[0], args[1]), { + initialProps: [mockAlertsQuery, indexName], }); await waitForNextUpdate(); await waitForNextUpdate(); if (result.current.setQuery) { - result.current.setQuery({ ...mockSignalsQuery }); + result.current.setQuery({ ...mockAlertsQuery }); } await waitForNextUpdate(); expect(spyOnfetchRules).toHaveBeenCalledTimes(2); @@ -107,13 +107,13 @@ describe('useQuerySignals', () => { }); test('if there is an error when fetching data, we should get back the init value for every properties', async () => { - const spyOnGetUserPrivilege = jest.spyOn(api, 'fetchQuerySignals'); + const spyOnGetUserPrivilege = jest.spyOn(api, 'fetchQueryAlerts'); spyOnGetUserPrivilege.mockImplementation(() => { throw new Error('Something went wrong, let see what happen'); }); await act(async () => { - const { result, waitForNextUpdate } = renderHook>( - () => useQuerySignals(mockSignalsQuery, 'mock-index-name') + const { result, waitForNextUpdate } = renderHook>( + () => useQueryAlerts(mockAlertsQuery, 'mock-index-name') ); await waitForNextUpdate(); await waitForNextUpdate(); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.tsx similarity index 69% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.tsx index 531e080ed7d1f..9c992fa872705 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.tsx +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.tsx @@ -6,14 +6,14 @@ import React, { SetStateAction, useEffect, useState } from 'react'; -import { fetchQuerySignals } from './api'; -import { SignalSearchResponse } from './types'; +import { fetchQueryAlerts } from './api'; +import { AlertSearchResponse } from './types'; type Func = () => void; -export interface ReturnQuerySignals { +export interface ReturnQueryAlerts { loading: boolean; - data: SignalSearchResponse | null; + data: AlertSearchResponse | null; setQuery: React.Dispatch>; response: string; request: string; @@ -21,18 +21,18 @@ export interface ReturnQuerySignals { } /** - * Hook for using to get a Signals from the Detection Engine API + * Hook for fetching Alerts from the Detection Engine API * * @param initialQuery query dsl object * */ -export const useQuerySignals = ( +export const useQueryAlerts = ( initialQuery: object, indexName?: string | null -): ReturnQuerySignals => { +): ReturnQueryAlerts => { const [query, setQuery] = useState(initialQuery); - const [signals, setSignals] = useState< - Pick, 'data' | 'setQuery' | 'response' | 'request' | 'refetch'> + const [alerts, setAlerts] = useState< + Pick, 'data' | 'setQuery' | 'response' | 'request' | 'refetch'> >({ data: null, response: '', @@ -49,15 +49,15 @@ export const useQuerySignals = ( async function fetchData() { try { setLoading(true); - const signalResponse = await fetchQuerySignals({ + const alertResponse = await fetchQueryAlerts({ query, signal: abortCtrl.signal, }); if (isSubscribed) { - setSignals({ - data: signalResponse, - response: JSON.stringify(signalResponse, null, 2), + setAlerts({ + data: alertResponse, + response: JSON.stringify(alertResponse, null, 2), request: JSON.stringify({ index: [indexName] ?? [''], body: query }, null, 2), setQuery, refetch: fetchData, @@ -65,7 +65,7 @@ export const useQuerySignals = ( } } catch (error) { if (isSubscribed) { - setSignals({ + setAlerts({ data: null, response: '', request: '', @@ -86,5 +86,5 @@ export const useQuerySignals = ( }; }, [query, indexName]); - return { loading, ...signals }; + return { loading, ...alerts }; }; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx similarity index 96% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx index c834e4ab14be2..d0571bfca5b2b 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx @@ -30,7 +30,7 @@ describe('useSignalIndex', () => { }); }); - test('fetch signals info', async () => { + test('fetch alerts info', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => useSignalIndex() @@ -105,7 +105,7 @@ describe('useSignalIndex', () => { }); }); - test('if there is an error when fetching signals info, signalIndexExists === false && signalIndexName == null', async () => { + test('if there is an error when fetching alerts info, signalIndexExists === false && signalIndexName == null', async () => { const spyOnGetSignalIndex = jest.spyOn(api, 'getSignalIndex'); spyOnGetSignalIndex.mockImplementation(() => { throw new Error('Something went wrong, let see what happen'); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/types.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/types.ts index 897568cdbf16e..ab9b88fb81fa7 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/rules/types.ts @@ -10,7 +10,7 @@ import { RuleTypeSchema } from '../../../../../common/detection_engine/types'; /** * Params is an "record", since it is a type of AlertActionParams which is action templates. - * @see x-pack/plugins/alerting/common/alert.ts + * @see x-pack/plugins/alerts/common/alert.ts */ export const action = t.exact( t.type({ diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx index a83a85678bd03..e3eb4666522ad 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx @@ -6,7 +6,6 @@ import { EuiButton, EuiSpacer } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { useParams } from 'react-router-dom'; import { StickyContainer } from 'react-sticky'; import { connect, ConnectedProps } from 'react-redux'; @@ -15,60 +14,34 @@ import { indicesExistOrDataTemporarilyUnavailable, WithSource, } from '../../../common/containers/source'; -import { AlertsTable } from '../../../common/components/alerts_viewer/alerts_table'; import { UpdateDateRange } from '../../../common/components/charts/common'; import { FiltersGlobal } from '../../../common/components/filters_global'; -import { - getDetectionEngineTabUrl, - getRulesUrl, -} from '../../../common/components/link_to/redirect_to_detection_engine'; +import { getRulesUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; -import { SiemNavigation } from '../../../common/components/navigation'; -import { NavTab } from '../../../common/components/navigation/types'; import { State } from '../../../common/store'; import { inputsSelectors } from '../../../common/store/inputs'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; import { InputsRange } from '../../../common/store/inputs/model'; -import { AlertsByCategory } from '../../../overview/components/alerts_by_category'; -import { useSignalInfo } from '../../components/signals_info'; -import { SignalsTable } from '../../components/signals'; +import { useAlertInfo } from '../../components/alerts_info'; +import { AlertsTable } from '../../components/alerts_table'; import { NoApiIntegrationKeyCallOut } from '../../components/no_api_integration_callout'; -import { NoWriteSignalsCallOut } from '../../components/no_write_signals_callout'; -import { SignalsHistogramPanel } from '../../components/signals_histogram_panel'; -import { signalsHistogramOptions } from '../../components/signals_histogram_panel/config'; +import { NoWriteAlertsCallOut } from '../../components/no_write_alerts_callout'; +import { AlertsHistogramPanel } from '../../components/alerts_histogram_panel'; +import { alertsHistogramOptions } from '../../components/alerts_histogram_panel/config'; import { useUserInfo } from '../../components/user_info'; import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page'; import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; import * as i18n from './translations'; -import { DetectionEngineTab } from './types'; - -const detectionsTabs: Record = { - [DetectionEngineTab.signals]: { - id: DetectionEngineTab.signals, - name: i18n.SIGNAL, - href: getDetectionEngineTabUrl(DetectionEngineTab.signals), - disabled: false, - urlKey: 'detections', - }, - [DetectionEngineTab.alerts]: { - id: DetectionEngineTab.alerts, - name: i18n.ALERT, - href: getDetectionEngineTabUrl(DetectionEngineTab.alerts), - disabled: false, - urlKey: 'detections', - }, -}; export const DetectionEnginePageComponent: React.FC = ({ filters, query, setAbsoluteRangeDatePicker, }) => { - const { tabName = DetectionEngineTab.signals } = useParams(); const { loading, isSignalIndexExists, @@ -79,7 +52,7 @@ export const DetectionEnginePageComponent: React.FC = ({ hasIndexWrite, } = useUserInfo(); - const [lastSignals] = useSignalInfo({}); + const [lastAlerts] = useAlertInfo({}); const updateDateRangeCallback = useCallback( ({ x }) => { @@ -116,7 +89,7 @@ export const DetectionEnginePageComponent: React.FC = ({ return ( <> {hasEncryptionKey != null && !hasEncryptionKey && } - {hasIndexWrite != null && !hasIndexWrite && } + {hasIndexWrite != null && !hasIndexWrite && } {({ indicesExist, indexPattern }) => { return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( @@ -127,11 +100,11 @@ export const DetectionEnginePageComponent: React.FC = ({ - {i18n.LAST_SIGNAL} + {i18n.LAST_ALERT} {': '} - {lastSignals} + {lastAlerts} ) } @@ -141,7 +114,7 @@ export const DetectionEnginePageComponent: React.FC = ({ fill href={getRulesUrl()} iconType="gear" - data-test-subj="manage-signal-detection-rules" + data-test-subj="manage-alert-detection-rules" > {i18n.BUTTON_MANAGE_RULES}
@@ -150,48 +123,29 @@ export const DetectionEnginePageComponent: React.FC = ({ {({ to, from, deleteQuery, setQuery }) => ( <> - - - {tabName === DetectionEngineTab.signals && ( - <> - - - - - )} - {tabName === DetectionEngineTab.alerts && ( - <> - - - - )} + <> + + + + )} diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx b/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx index 756e222c02950..1f9b1373d404d 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx @@ -13,7 +13,6 @@ import { DetectionEnginePage } from './detection_engine'; import { EditRulePage } from './rules/edit'; import { RuleDetailsPage } from './rules/details'; import { RulesPage } from './rules'; -import { DetectionEngineTab } from './types'; const detectionEnginePath = `/:pageName(detections)`; @@ -22,11 +21,7 @@ type Props = Partial> & { url: string }; const DetectionEngineContainerComponent: React.FC = () => ( - + @@ -44,7 +39,7 @@ const DetectionEngineContainerComponent: React.FC = () => ( ( - + )} /> diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts index 1894d0ab1a9e7..d9cbcfc8979a1 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts @@ -611,7 +611,7 @@ describe('helpers', () => { const mockAction = { group: 'default', id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', - params: { message: 'ML Rule generated {{state.signals_count}} signals' }, + params: { message: 'ML Rule generated {{state.signals_count}} alerts' }, actionTypeId: '.slack', }; diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts index f35b6c8d7b00e..615882d4a7e3b 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts @@ -13,7 +13,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule. export const BACK_TO_RULES = i18n.translate( 'xpack.siem.detectionEngine.createRule.backToRulesDescription', { - defaultMessage: 'Back to signal detection rules', + defaultMessage: 'Back to detection rules', } ); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx index 74110e25cc940..7197ed397717c 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx @@ -42,15 +42,15 @@ import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details'; import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page'; -import { SignalsHistogramPanel } from '../../../../components/signals_histogram_panel'; -import { SignalsTable } from '../../../../components/signals'; +import { AlertsHistogramPanel } from '../../../../components/alerts_histogram_panel'; +import { AlertsTable } from '../../../../components/alerts_table'; import { useUserInfo } from '../../../../components/user_info'; import { DetectionEngineEmptyPage } from '../../detection_engine_empty_page'; -import { useSignalInfo } from '../../../../components/signals_info'; +import { useAlertInfo } from '../../../../components/alerts_info'; import { StepDefineRule } from '../../../../components/rules/step_define_rule'; import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; -import { buildSignalsRuleIdFilter } from '../../../../components/signals/default_config'; -import { NoWriteSignalsCallOut } from '../../../../components/no_write_signals_callout'; +import { buildAlertsRuleIdFilter } from '../../../../components/alerts_table/default_config'; +import { NoWriteAlertsCallOut } from '../../../../components/no_write_alerts_callout'; import * as detectionI18n from '../../translations'; import { ReadOnlyCallOut } from '../../../../components/rules/read_only_callout'; import { RuleSwitch } from '../../../../components/rules/rule_switch'; @@ -59,7 +59,7 @@ import { getStepsData, redirectToDetections, userHasNoPermissions } from '../hel import * as ruleI18n from '../translations'; import * as i18n from './translations'; import { GlobalTime } from '../../../../../common/containers/global_time'; -import { signalsHistogramOptions } from '../../../../components/signals_histogram_panel/config'; +import { alertsHistogramOptions } from '../../../../components/alerts_histogram_panel/config'; import { inputsSelectors } from '../../../../../common/store/inputs'; import { State } from '../../../../../common/store'; import { InputsRange } from '../../../../../common/store/inputs/model'; @@ -72,14 +72,14 @@ import { useMlCapabilities } from '../../../../../common/components/ml_popover/h import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions'; enum RuleDetailTabs { - signals = 'signals', + alerts = 'alerts', failures = 'failures', } const ruleDetailTabs = [ { - id: RuleDetailTabs.signals, - name: detectionI18n.SIGNAL, + id: RuleDetailTabs.alerts, + name: detectionI18n.ALERT, disabled: false, }, { @@ -107,7 +107,7 @@ export const RuleDetailsPageComponent: FC = ({ const [isLoading, rule] = useRule(ruleId); // This is used to re-trigger api rule status when user de/activate rule const [ruleEnabled, setRuleEnabled] = useState(null); - const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals); + const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.alerts); const { aboutRuleData, modifiedAboutRuleDetailsData, defineRuleData, scheduleRuleData } = rule != null ? getStepsData({ rule, detailsView: true }) @@ -117,7 +117,7 @@ export const RuleDetailsPageComponent: FC = ({ defineRuleData: null, scheduleRuleData: null, }; - const [lastSignals] = useSignalInfo({ ruleId }); + const [lastAlerts] = useAlertInfo({ ruleId }); const mlCapabilities = useMlCapabilities(); // TODO: Refactor license check + hasMlAdminPermissions to common check @@ -166,13 +166,13 @@ export const RuleDetailsPageComponent: FC = ({ [isLoading, rule] ); - const signalDefaultFilters = useMemo( - () => (ruleId != null ? buildSignalsRuleIdFilter(ruleId) : []), + const alertDefaultFilters = useMemo( + () => (ruleId != null ? buildAlertsRuleIdFilter(ruleId) : []), [ruleId] ); - const signalMergedFilters = useMemo(() => [...signalDefaultFilters, ...filters], [ - signalDefaultFilters, + const alertMergedFilters = useMemo(() => [...alertDefaultFilters, ...filters], [ + alertDefaultFilters, filters, ]); @@ -196,7 +196,7 @@ export const RuleDetailsPageComponent: FC = ({ const ruleError = useMemo( () => rule?.status === 'failed' && - ruleDetailTab === RuleDetailTabs.signals && + ruleDetailTab === RuleDetailTabs.alerts && rule?.last_failure_at != null ? ( = ({ return ( <> - {hasIndexWrite != null && !hasIndexWrite && } + {hasIndexWrite != null && !hasIndexWrite && } {userHasNoPermissions(canUserCRUD) && } {({ indicesExist, indexPattern }) => { @@ -257,12 +257,12 @@ export const RuleDetailsPageComponent: FC = ({ border subtitle={subTitle} subtitle2={[ - ...(lastSignals != null + ...(lastAlerts != null ? [ <> - {detectionI18n.LAST_SIGNAL} + {detectionI18n.LAST_ALERT} {': '} - {lastSignals} + {lastAlerts} , ] : []), @@ -358,24 +358,24 @@ export const RuleDetailsPageComponent: FC = ({ {tabs} - {ruleDetailTab === RuleDetailTabs.signals && ( + {ruleDetailTab === RuleDetailTabs.alerts && ( <> - {ruleId != null && ( - { diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts index fc0a79fa652ff..0fe1106171054 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts @@ -6,12 +6,9 @@ import { i18n } from '@kbn/i18n'; -export const BACK_TO_DETECTION_ENGINE = i18n.translate( - 'xpack.siem.detectionEngine.rules.backOptionsHeader', - { - defaultMessage: 'Back to detections', - } -); +export const BACK_TO_ALERTS = i18n.translate('xpack.siem.detectionEngine.rules.backOptionsHeader', { + defaultMessage: 'Back to alerts', +}); export const IMPORT_RULE = i18n.translate('xpack.siem.detectionEngine.rules.importRuleTitle', { defaultMessage: 'Import rule…', @@ -22,7 +19,7 @@ export const ADD_NEW_RULE = i18n.translate('xpack.siem.detectionEngine.rules.add }); export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', { - defaultMessage: 'Signal detection rules', + defaultMessage: 'Detection rules', }); export const ADD_PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.addPageTitle', { diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/types.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/types.ts index 92c9780a11722..5f81409010a28 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/types.ts @@ -5,7 +5,7 @@ */ import { RuleAlertAction, RuleType } from '../../../../../common/detection_engine/types'; -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { Filter } from '../../../../../../../../src/plugins/data/common'; import { FormData, FormHook } from '../../../../shared_imports'; import { FieldValueQueryBar } from '../../../components/rules/query_bar'; diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts index 34a521ed32b12..3d2f2dc03946a 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts @@ -19,6 +19,6 @@ describe('getBreadcrumbs', () => { }, [] ) - ).toEqual([{ href: '#/link-to/detections', text: 'Detections' }]); + ).toEqual([{ href: '#/link-to/detections', text: 'Alerts' }]); }); }); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts index 159301a07de78..e5cdbd7123ff4 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts @@ -30,13 +30,6 @@ const getTabBreadcrumb = (pathname: string, search: string[]) => { }; } - if (tabPath === 'signals') { - return { - text: i18nDetections.SIGNAL, - href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`, - }; - } - if (tabPath === 'rules') { return { text: i18nRules.PAGE_TITLE, diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts index 008d660be9d88..067399f68d51a 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts @@ -7,27 +7,23 @@ import { i18n } from '@kbn/i18n'; export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.detectionsPageTitle', { - defaultMessage: 'Detections', + defaultMessage: 'Alerts', }); -export const LAST_SIGNAL = i18n.translate('xpack.siem.detectionEngine.lastSignalTitle', { - defaultMessage: 'Last signal', +export const LAST_ALERT = i18n.translate('xpack.siem.detectionEngine.lastAlertTitle', { + defaultMessage: 'Last alert', }); -export const TOTAL_SIGNAL = i18n.translate('xpack.siem.detectionEngine.totalSignalTitle', { +export const TOTAL_ALERT = i18n.translate('xpack.siem.detectionEngine.totalAlertTitle', { defaultMessage: 'Total', }); -export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', { - defaultMessage: 'Detected signals', -}); - export const ALERT = i18n.translate('xpack.siem.detectionEngine.alertTitle', { - defaultMessage: 'External alerts', + defaultMessage: 'Detected alerts', }); export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', { - defaultMessage: 'Manage signal detection rules', + defaultMessage: 'Manage detection rules', }); export const PANEL_SUBTITLE_SHOWING = i18n.translate( diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts deleted file mode 100644 index d529d99ad3ad4..0000000000000 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export enum DetectionEngineTab { - signals = 'signals', - alerts = 'alerts', -} diff --git a/x-pack/plugins/siem/public/app/types.ts b/x-pack/plugins/siem/public/app/types.ts index 444e0066c3c7b..4b00dee3b7510 100644 --- a/x-pack/plugins/siem/public/app/types.ts +++ b/x-pack/plugins/siem/public/app/types.ts @@ -4,17 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer, AnyAction, Middleware, Dispatch } from 'redux'; +import { + Reducer, + AnyAction, + Middleware, + Dispatch, + PreloadedState, + StateFromReducersMapObject, + CombinedState, +} from 'redux'; + import { NavTab } from '../common/components/navigation/types'; -import { HostsState } from '../hosts/store'; -import { NetworkState } from '../network/store'; -import { TimelineState } from '../timelines/store/timeline/types'; -import { ImmutableReducer, State } from '../common/store'; +import { State, SubPluginsInitReducer } from '../common/store'; import { Immutable } from '../../common/endpoint/types'; -import { AlertListState } from '../../common/endpoint_alerts/types'; import { AppAction } from '../common/store/actions'; -import { HostState } from '../endpoint_hosts/types'; -import { ManagementState } from '../management/store/types'; export enum SiemPageName { overview = 'overview', @@ -38,7 +41,7 @@ export type SiemNavTabKey = export type SiemNavTab = Record; export interface SecuritySubPluginStore { - initialState: Record; + initialState: Record; reducer: Record>; middleware?: Array>>>; } @@ -54,6 +57,10 @@ type SecuritySubPluginKeyStore = | 'hostList' | 'alertList' | 'management'; + +/** + * Returned by the various 'SecuritySubPlugin' classes from the `start` method. + */ export interface SecuritySubPluginWithStore extends SecuritySubPlugin { store: SecuritySubPluginStore; @@ -61,22 +68,17 @@ export interface SecuritySubPluginWithStore; - hostList: Immutable; - management: ManagementState; - }; - reducer: { - hosts: Reducer; - network: Reducer; - timeline: Reducer; - alertList: ImmutableReducer; - hostList: ImmutableReducer; - management: ImmutableReducer; - }; + initialState: PreloadedState< + CombinedState< + StateFromReducersMapObject< + /** SubPluginsInitReducer, being an interface, will not work in `StateFromReducersMapObject`. + * Picking its keys does the trick. + **/ + Pick + > + > + >; + reducer: SubPluginsInitReducer; middlewares: Array>>>; }; } diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx index 3bd2a3da1c88b..c33677e41db0e 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/plugins/siem/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -15,7 +15,7 @@ import { BrowserFields } from '../../containers/source'; import { dragAndDropModel, dragAndDropSelectors } from '../../store'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { IdToDataProvider } from '../../store/drag_and_drop/model'; -import { State } from '../../store/reducer'; +import { State } from '../../store/types'; import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; import { reArrangeProviders } from '../../../timelines/components/timeline/data_providers/helpers'; import { ACTIVE_TIMELINE_REDUX_ID } from '../top_n'; @@ -27,6 +27,7 @@ import { addFieldToTimelineColumns, addProviderToTimeline, fieldWasDroppedOnTimelineColumns, + getTimelineIdFromColumnDroppableId, IS_DRAGGING_CLASS_NAME, IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME, providerWasDroppedOnTimeline, @@ -82,7 +83,7 @@ const onDragEndHandler = ({ browserFields, dispatch, result, - timelineId: ACTIVE_TIMELINE_REDUX_ID, + timelineId: getTimelineIdFromColumnDroppableId(result.destination?.droppableId ?? ''), }); } }; diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.test.ts b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.test.ts index 69fbedb6462cb..be58381fbca1b 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.test.ts +++ b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.test.ts @@ -32,6 +32,7 @@ import { getDroppableId, getFieldIdFromDraggable, getProviderIdFromDraggable, + getTimelineIdFromColumnDroppableId, getTimelineProviderDraggableId, getTimelineProviderDroppableId, providerWasDroppedOnTimeline, @@ -984,4 +985,16 @@ describe('helpers', () => { }); }); }); + + describe('getTimelineIdFromColumnDroppableId', () => { + test('it returns the expected timelineId from a column droppableId', () => { + expect(getTimelineIdFromColumnDroppableId(DROPPABLE_ID_TIMELINE_COLUMNS)).toEqual( + 'timeline-1' + ); + }); + + test('it returns an empty string when the droppableId is an empty string', () => { + expect(getTimelineIdFromColumnDroppableId('')).toEqual(''); + }); + }); }); diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts index ad370f647738f..4fb4e5d30ca7a 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts +++ b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts @@ -258,7 +258,7 @@ export const allowTopN = ({ 'string', ].includes(fieldType); - // TODO: remove this explicit whitelist when the ECS documentation includes signals + // TODO: remove this explicit whitelist when the ECS documentation includes alerts const isWhitelistedNonBrowserField = [ 'signal.ancestors.depth', 'signal.ancestors.id', @@ -332,3 +332,6 @@ export const allowTopN = ({ return isWhitelistedNonBrowserField || (isAggregatable && isAllowedType); }; + +export const getTimelineIdFromColumnDroppableId = (droppableId: string) => + droppableId.slice(droppableId.lastIndexOf('.') + 1); diff --git a/x-pack/plugins/siem/public/common/components/error_toast_dispatcher/index.test.tsx b/x-pack/plugins/siem/public/common/components/error_toast_dispatcher/index.test.tsx index 50b20099b17d0..39b17f7008e64 100644 --- a/x-pack/plugins/siem/public/common/components/error_toast_dispatcher/index.test.tsx +++ b/x-pack/plugins/siem/public/common/components/error_toast_dispatcher/index.test.tsx @@ -12,7 +12,7 @@ import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '.. import { createStore } from '../../store/store'; import { ErrorToastDispatcher } from '.'; -import { State } from '../../store/reducer'; +import { State } from '../../store/types'; describe('Error Toast Dispatcher', () => { const state: State = mockGlobalState; diff --git a/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx b/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx index 8151291679e32..0294d175aef19 100644 --- a/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx +++ b/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx @@ -25,7 +25,6 @@ import { RedirectToCreatePage, RedirectToConfigureCasesPage, } from './redirect_to_case'; -import { DetectionEngineTab } from '../../../alerts/pages/detection_engine/types'; import { TimelineType } from '../../../../common/types/timeline'; import { RedirectToManagementPage } from './redirect_to_management'; @@ -89,11 +88,6 @@ export const LinkToPage = React.memo(({ match }) => ( exact path={`${match.url}/:pageName(${SiemPageName.detections})`} /> - ; @@ -20,14 +18,9 @@ export type DetectionEngineComponentProps = RouteComponentProps<{ export const DETECTION_ENGINE_PAGE_NAME = 'detections'; export const RedirectToDetectionEnginePage = ({ - match: { - params: { tabName }, - }, location: { search }, }: DetectionEngineComponentProps) => { - const defaultSelectedTab = DetectionEngineTab.signals; - const selectedTab = tabName ? tabName : defaultSelectedTab; - const to = `/${DETECTION_ENGINE_PAGE_NAME}/${selectedTab}${search}`; + const to = `/${DETECTION_ENGINE_PAGE_NAME}${search}`; return ; }; @@ -66,8 +59,6 @@ const baseDetectionEngineUrl = `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; export const getDetectionEngineUrl = (search?: string) => `${baseDetectionEngineUrl}${appendSearch(search)}`; -export const getDetectionEngineAlertUrl = (search?: string) => - `${baseDetectionEngineUrl}/${DetectionEngineTab.alerts}${appendSearch(search)}`; export const getDetectionEngineTabUrl = (tabPath: string) => `${baseDetectionEngineUrl}/${tabPath}`; export const getRulesUrl = () => `${baseDetectionEngineUrl}/rules`; export const getCreateRuleUrl = () => `${baseDetectionEngineUrl}/rules/create`; diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap index 46e61f9e939ee..78b05a00cef9b 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap +++ b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap @@ -5,7 +5,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` size="s" > diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/popover_description.tsx b/x-pack/plugins/siem/public/common/components/ml_popover/popover_description.tsx index a491d4b6b769c..7f85f728a0fab 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/popover_description.tsx +++ b/x-pack/plugins/siem/public/common/components/ml_popover/popover_description.tsx @@ -14,7 +14,7 @@ export const PopoverDescriptionComponent = () => ( diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx b/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx index eda6b0f28499c..e9efd7df23903 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx +++ b/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx @@ -59,7 +59,7 @@ export const UpgradeContentsComponent = () => (
diff --git a/x-pack/plugins/siem/public/common/components/news_feed/no_news/index.tsx b/x-pack/plugins/siem/public/common/components/news_feed/no_news/index.tsx index c4e0482c6b30a..8061a8f9799e3 100644 --- a/x-pack/plugins/siem/public/common/components/news_feed/no_news/index.tsx +++ b/x-pack/plugins/siem/public/common/components/news_feed/no_news/index.tsx @@ -13,7 +13,7 @@ export const NoNews = React.memo(() => ( <> {i18n.NO_NEWS_MESSAGE}{' '} - + {i18n.ADVANCED_SETTINGS_LINK_TITLE} {'.'} diff --git a/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx b/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx index da0f6f59b533f..a9a0b85202fee 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { allEvents, defaultOptions, getOptions, rawEvents, signalEvents } from './helpers'; +import { allEvents, defaultOptions, getOptions, rawEvents, alertEvents } from './helpers'; describe('getOptions', () => { test(`it returns the default options when 'activeTimelineEventType' is undefined`, () => { @@ -19,7 +19,7 @@ describe('getOptions', () => { expect(getOptions('raw')).toEqual(rawEvents); }); - test(`it returns 'signalEvents' when 'activeTimelineEventType' is 'signal'`, () => { - expect(getOptions('signal')).toEqual(signalEvents); + test(`it returns 'alertEvents' when 'activeTimelineEventType' is 'alert'`, () => { + expect(getOptions('alert')).toEqual(alertEvents); }); }); diff --git a/x-pack/plugins/siem/public/common/components/top_n/helpers.ts b/x-pack/plugins/siem/public/common/components/top_n/helpers.ts index a4226cc58530a..b654eaf17b47b 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/helpers.ts +++ b/x-pack/plugins/siem/public/common/components/top_n/helpers.ts @@ -32,17 +32,17 @@ export const rawEvents: TopNOption[] = [ }, ]; -/** A (stable) array containing only the 'Signal events' option */ -export const signalEvents: TopNOption[] = [ +/** A (stable) array containing only the 'Alert events' option */ +export const alertEvents: TopNOption[] = [ { - value: 'signal', - inputDisplay: i18n.SIGNAL_EVENTS, - 'data-test-subj': 'option-signal', + value: 'alert', + inputDisplay: i18n.ALERT_EVENTS, + 'data-test-subj': 'option-alert', }, ]; /** A (stable) array containing the default Top N options */ -export const defaultOptions = [...rawEvents, ...signalEvents]; +export const defaultOptions = [...rawEvents, ...alertEvents]; /** * Returns the options to be displayed in a Top N view select. When @@ -58,8 +58,8 @@ export const getOptions = (activeTimelineEventType?: EventType): TopNOption[] => return allEvents; case 'raw': return rawEvents; - case 'signal': - return signalEvents; + case 'alert': + return alertEvents; default: return defaultOptions; } diff --git a/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx b/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx index d2b38a062091e..0505f55d507b8 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx @@ -310,13 +310,13 @@ describe('StatefulTopN', () => { }); }); - test(`defaults to the 'Signals events' option when rendering in a NON-active timeline context (e.g. the Signals table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'signals'`, () => { + test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, () => { const filterManager = new FilterManager(mockUiSettingsForFilterManager); const wrapper = mount( { const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props; - expect(props.defaultView).toEqual('signal'); + expect(props.defaultView).toEqual('alert'); }); }); diff --git a/x-pack/plugins/siem/public/common/components/top_n/index.tsx b/x-pack/plugins/siem/public/common/components/top_n/index.tsx index a71b27e0bd9cb..f6dc8e7c66ed0 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/index.tsx @@ -94,14 +94,14 @@ const StatefulTopNComponent: React.FC = ({ const kibana = useKibana(); // Regarding data from useTimelineTypeContext: - // * `documentType` (e.g. 'signals') may only be populated in some views, - // e.g. the `Signals` view on the `Detections` page. + // * `documentType` (e.g. 'alerts') may only be populated in some views, + // e.g. the `Alerts` view on the `Detections` page. // * `id` (`timelineId`) may only be populated when we are rendered in the // context of the active timeline. - // * `indexToAdd`, which enables the signals index to be appended to + // * `indexToAdd`, which enables the alerts index to be appended to // the `indexPattern` returned by `WithSource`, may only be populated when // this component is rendered in the context of the active timeline. This - // behavior enables the 'All events' view by appending the signals index + // behavior enables the 'All events' view by appending the alerts index // to the index pattern. const { documentType, id: timelineId, indexToAdd } = useTimelineTypeContext(); @@ -135,7 +135,7 @@ const StatefulTopNComponent: React.FC = ({ } data-test-subj="top-n" defaultView={ - documentType?.toLocaleLowerCase() === 'signals' ? 'signal' : options[0].value + documentType?.toLocaleLowerCase() === 'alerts' ? 'alert' : options[0].value } deleteQuery={timelineId === ACTIVE_TIMELINE_REDUX_ID ? undefined : deleteQuery} field={field} diff --git a/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx b/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx index 0a35b9e25754c..5e1476f62216b 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx @@ -163,11 +163,11 @@ describe('TopN', () => { }); test(`it does NOT render SignalsByCategory when defaultView is 'raw'`, () => { - expect(wrapper.find('[data-test-subj="signals-histogram-panel"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(false); }); }); - describe('signals view', () => { + describe('alerts view', () => { let toggleTopN: () => void; let wrapper: ReactWrapper; @@ -176,7 +176,7 @@ describe('TopN', () => { wrapper = mount( { }); test(`it renders SignalsByCategory when defaultView is 'signal'`, () => { - expect(wrapper.find('[data-test-subj="signals-histogram-panel"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(true); }); test(`it does NOT render EventsByDataset when defaultView is 'signal'`, () => { @@ -242,7 +242,7 @@ describe('TopN', () => { }); test(`it does NOT render SignalsByCategory when defaultView is 'all'`, () => { - expect(wrapper.find('[data-test-subj="signals-histogram-panel"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(false); }); }); }); diff --git a/x-pack/plugins/siem/public/common/components/top_n/translations.ts b/x-pack/plugins/siem/public/common/components/top_n/translations.ts index 7db55fa94d42e..a6873ab0ece7b 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/translations.ts +++ b/x-pack/plugins/siem/public/common/components/top_n/translations.ts @@ -18,6 +18,6 @@ export const RAW_EVENTS = i18n.translate('xpack.siem.topN.rawEventsSelectLabel', defaultMessage: 'Raw events', }); -export const SIGNAL_EVENTS = i18n.translate('xpack.siem.topN.signalEventsSelectLabel', { - defaultMessage: 'Signal events', +export const ALERT_EVENTS = i18n.translate('xpack.siem.topN.alertEventsSelectLabel', { + defaultMessage: 'Alert events', }); diff --git a/x-pack/plugins/siem/public/common/mock/endpoint/app_context_render.tsx b/x-pack/plugins/siem/public/common/mock/endpoint/app_context_render.tsx index e62f36c2ec782..3c0189625ee29 100644 --- a/x-pack/plugins/siem/public/common/mock/endpoint/app_context_render.tsx +++ b/x-pack/plugins/siem/public/common/mock/endpoint/app_context_render.tsx @@ -15,10 +15,10 @@ import { depsStartMock } from './dependencies_start_mock'; import { MiddlewareActionSpyHelper, createSpyMiddleware } from '../../store/test_utils'; import { apolloClientObservable } from '../test_providers'; import { createStore, State, substateMiddlewareFactory } from '../../store'; -import { hostMiddlewareFactory } from '../../../endpoint_hosts/store'; import { alertMiddlewareFactory } from '../../../endpoint_alerts/store/middleware'; import { AppRootProvider } from './app_root_provider'; -import { managementMiddlewareFactory } from '../../../management/store'; +import { managementMiddlewareFactory } from '../../../management/store/middleware'; +import { hostMiddlewareFactory } from '../../../endpoint_hosts/store/middleware'; import { SUB_PLUGINS_REDUCER, mockGlobalState } from '..'; type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; @@ -56,8 +56,7 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { const coreStart = coreMock.createStart({ basePath: '/mock' }); const depsStart = depsStartMock(); const middlewareSpy = createSpyMiddleware(); - const state: State = mockGlobalState; - const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, [ + const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable, [ substateMiddlewareFactory( (globalState) => globalState.hostList, hostMiddlewareFactory(coreStart, depsStart) @@ -76,7 +75,6 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { ); const render: UiRender = (ui, options) => { - // @ts-ignore return reactRender(ui, { wrapper: AppWrapper as React.ComponentType, ...options, diff --git a/x-pack/plugins/siem/public/common/mock/global_state.ts b/x-pack/plugins/siem/public/common/mock/global_state.ts index c96f67a39dbfe..30dffa8dbf6bf 100644 --- a/x-pack/plugins/siem/public/common/mock/global_state.ts +++ b/x-pack/plugins/siem/public/common/mock/global_state.ts @@ -27,11 +27,10 @@ import { networkModel } from '../../network/store'; import { TimelineType, TimelineStatus } from '../../../common/types/timeline'; import { initialAlertListState } from '../../endpoint_alerts/store/reducer'; import { initialHostListState } from '../../endpoint_hosts/store/reducer'; -import { getManagementInitialState } from '../../management/store'; - -const alertList = initialAlertListState(); -const hostList = initialHostListState(); -const management = getManagementInitialState(); +import { mockManagementState } from '../../management/store/reducer'; +import { AlertListState } from '../../../common/endpoint_alerts/types'; +import { HostState } from '../../endpoint_hosts/types'; +import { ManagementState } from '../../management/types'; export const mockGlobalState: State = { app: { @@ -233,7 +232,11 @@ export const mockGlobalState: State = { }, }, }, - alertList, - hostList, - management, + /** + * These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture, + * they are cast to mutable versions here. + */ + alertList: initialAlertListState as AlertListState, + hostList: initialHostListState as HostState, + management: mockManagementState as ManagementState, }; diff --git a/x-pack/plugins/siem/public/common/mock/mock_ecs.ts b/x-pack/plugins/siem/public/common/mock/mock_ecs.ts index 7fbbabb29da1b..c897da69caba0 100644 --- a/x-pack/plugins/siem/public/common/mock/mock_ecs.ts +++ b/x-pack/plugins/siem/public/common/mock/mock_ecs.ts @@ -1281,7 +1281,7 @@ export const mockEcsData: Ecs[] = [ }, ]; -export const mockEcsDataWithSignal: Ecs = { +export const mockEcsDataWithAlert: Ecs = { _id: '1', timestamp: '2018-11-05T19:03:25.937Z', host: { diff --git a/x-pack/plugins/siem/public/common/mock/timeline_results.ts b/x-pack/plugins/siem/public/common/mock/timeline_results.ts index 42d14daa11a6b..4eb66acdfad65 100644 --- a/x-pack/plugins/siem/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/siem/public/common/mock/timeline_results.ts @@ -10,7 +10,7 @@ import { TimelineType, TimelineStatus } from '../../../common/types/timeline'; import { OpenTimelineResult } from '../../timelines/components/open_timeline/types'; import { GetAllTimeline, SortFieldTimeline, TimelineResult, Direction } from '../../graphql/types'; import { allTimelinesQuery } from '../../timelines/containers/all/index.gql_query'; -import { CreateTimelineProps } from '../../alerts/components/signals/types'; +import { CreateTimelineProps } from '../../alerts/components/alerts_table/types'; import { TimelineModel } from '../../timelines/store/timeline/model'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; export interface MockedProvidedQuery { @@ -2206,7 +2206,7 @@ export const defaultTimelineProps: CreateTimelineProps = { enabled: true, excluded: false, id: - 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-1', + 'send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-1', kqlQuery: '', name: '1', queryMatch: { field: '_id', operator: ':', value: '1' }, diff --git a/x-pack/plugins/siem/public/common/mock/utils.ts b/x-pack/plugins/siem/public/common/mock/utils.ts index 532637acab767..1ff5cb8e734ec 100644 --- a/x-pack/plugins/siem/public/common/mock/utils.ts +++ b/x-pack/plugins/siem/public/common/mock/utils.ts @@ -7,9 +7,13 @@ import { hostsReducer } from '../../hosts/store'; import { networkReducer } from '../../network/store'; import { timelineReducer } from '../../timelines/store/timeline/reducer'; -import { hostListReducer } from '../../endpoint_hosts/store'; -import { alertListReducer } from '../../endpoint_alerts/store'; -import { managementReducer } from '../../management/store'; +import { managementReducer } from '../../management/store/reducer'; +import { ManagementPluginReducer } from '../../management'; +import { SubPluginsInitReducer } from '../store'; +import { EndpointAlertsPluginReducer } from '../../endpoint_alerts'; +import { EndpointHostsPluginReducer } from '../../endpoint_hosts'; +import { alertListReducer } from '../../endpoint_alerts/store/reducer'; +import { hostListReducer } from '../../endpoint_hosts/store/reducer'; interface Global extends NodeJS.Global { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -18,11 +22,15 @@ interface Global extends NodeJS.Global { export const globalNode: Global = global; -export const SUB_PLUGINS_REDUCER = { +export const SUB_PLUGINS_REDUCER: SubPluginsInitReducer = { hosts: hostsReducer, network: networkReducer, timeline: timelineReducer, - hostList: hostListReducer, - alertList: alertListReducer, - management: managementReducer, + /** + * These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture, + * they are cast to mutable versions here. + */ + hostList: hostListReducer as EndpointHostsPluginReducer['hostList'], + alertList: alertListReducer as EndpointAlertsPluginReducer['alertList'], + management: managementReducer as ManagementPluginReducer['management'], }; diff --git a/x-pack/plugins/siem/public/common/store/app/selectors.ts b/x-pack/plugins/siem/public/common/store/app/selectors.ts index c37695c2ccbe6..d18cb73dbcfb9 100644 --- a/x-pack/plugins/siem/public/common/store/app/selectors.ts +++ b/x-pack/plugins/siem/public/common/store/app/selectors.ts @@ -7,11 +7,9 @@ import { keys } from 'lodash/fp'; import memoizeOne from 'memoize-one'; import { createSelector } from 'reselect'; - import { Note } from '../../lib/note'; -import { State } from '../reducer'; - import { ErrorModel, NotesById } from './model'; +import { State } from '../types'; const selectNotesById = (state: State): NotesById => state.app.notesById; diff --git a/x-pack/plugins/siem/public/common/store/drag_and_drop/selectors.ts b/x-pack/plugins/siem/public/common/store/drag_and_drop/selectors.ts index 99a8369783cdd..5d6534f96bc7a 100644 --- a/x-pack/plugins/siem/public/common/store/drag_and_drop/selectors.ts +++ b/x-pack/plugins/siem/public/common/store/drag_and_drop/selectors.ts @@ -5,10 +5,8 @@ */ import { createSelector } from 'reselect'; - -import { State } from '../reducer'; - import { IdToDataProvider } from './model'; +import { State } from '../types'; const selectDataProviders = (state: State): IdToDataProvider => state.dragAndDrop.dataProviders; diff --git a/x-pack/plugins/siem/public/common/store/index.ts b/x-pack/plugins/siem/public/common/store/index.ts index 96eb5e159909c..6227931b53268 100644 --- a/x-pack/plugins/siem/public/common/store/index.ts +++ b/x-pack/plugins/siem/public/common/store/index.ts @@ -8,18 +8,46 @@ export * from './model'; export * from './reducer'; export * from './selectors'; +import { Middleware, Dispatch } from 'redux'; import { createStore, getStore } from './store'; -import { SubstateMiddlewareFactory } from './types'; +import { ImmutableMiddleware, State } from './types'; +import { AppAction } from './actions'; +import { Immutable } from '../../../common/endpoint/types'; export { createStore, getStore }; -export const substateMiddlewareFactory: SubstateMiddlewareFactory = (selector, middleware) => { +/** + * Takes a selector and an `ImmutableMiddleware`. The + * middleware's version of `getState` will receive + * the result of the selector instead of the global state. + * + * This allows middleware to have knowledge of only a subsection of state. + * + * `selector` returns an `Immutable` version of the substate. + * `middleware` must be an `ImmutableMiddleware`. + * + * Returns a regular middleware, meant to be used with `applyMiddleware`. + */ +export const substateMiddlewareFactory = ( + selector: (state: State) => Substate | Immutable, + middleware: ImmutableMiddleware +): Middleware<{}, State, Dispatch>> => { return (api) => { const substateAPI = { ...api, - // Return just the substate instead of global state. - getState() { - return selector(api.getState()); + // Return the substate instead of global state. + getState(): Immutable { + /** + * The selector will receive the basic (mutable) version of state. This is because + * the top level state shape doesn't use `Immutable`. We cast the return value as `Immutable` + * so that the middleware won't be able to mutate state. + * + * Immutable enforces nothing structural about a type so casting + * a value as `Immutable` is safe as long as nothing else is going to mutate. + * Since the state came from the return value of a reducer, the reducer will (hopefully) + * not be mutating it. + */ + return selector(api.getState()) as Immutable; }, }; return middleware(substateAPI); diff --git a/x-pack/plugins/siem/public/common/store/inputs/selectors.ts b/x-pack/plugins/siem/public/common/store/inputs/selectors.ts index 95c463776e288..0eee5ebbfbf77 100644 --- a/x-pack/plugins/siem/public/common/store/inputs/selectors.ts +++ b/x-pack/plugins/siem/public/common/store/inputs/selectors.ts @@ -6,7 +6,7 @@ import { createSelector } from 'reselect'; -import { State } from '../reducer'; +import { State } from '../types'; import { InputsModel, InputsRange, GlobalQuery } from './model'; diff --git a/x-pack/plugins/siem/public/common/store/reducer.ts b/x-pack/plugins/siem/public/common/store/reducer.ts index e06543b8d7181..ba85fbef860d7 100644 --- a/x-pack/plugins/siem/public/common/store/reducer.ts +++ b/x-pack/plugins/siem/public/common/store/reducer.ts @@ -4,47 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineReducers } from 'redux'; +import { combineReducers, PreloadedState, AnyAction, Reducer } from 'redux'; -import { appReducer, AppState, initialAppState } from './app'; -import { dragAndDropReducer, DragAndDropState, initialDragAndDropState } from './drag_and_drop'; -import { createInitialInputsState, initialInputsState, inputsReducer, InputsState } from './inputs'; +import { appReducer, initialAppState } from './app'; +import { dragAndDropReducer, initialDragAndDropState } from './drag_and_drop'; +import { createInitialInputsState, inputsReducer } from './inputs'; -import { HostsPluginState, HostsPluginReducer } from '../../hosts/store'; -import { NetworkPluginState, NetworkPluginReducer } from '../../network/store'; -import { TimelinePluginState, TimelinePluginReducer } from '../../timelines/store/timeline'; -import { - EndpointAlertsPluginState, - EndpointAlertsPluginReducer, -} from '../../endpoint_alerts/store'; -import { EndpointHostsPluginState, EndpointHostsPluginReducer } from '../../endpoint_hosts/store'; +import { HostsPluginReducer } from '../../hosts/store'; +import { NetworkPluginReducer } from '../../network/store'; +import { TimelinePluginReducer } from '../../timelines/store/timeline'; -import { ManagementPluginReducer, ManagementPluginState } from '../../management/store/types'; - -export interface State - extends HostsPluginState, - NetworkPluginState, - TimelinePluginState, - EndpointAlertsPluginState, - EndpointHostsPluginState, - ManagementPluginState { - app: AppState; - dragAndDrop: DragAndDropState; - inputs: InputsState; -} - -export const initialState: Pick = { - app: initialAppState, - dragAndDrop: initialDragAndDropState, - inputs: initialInputsState, -}; - -type SubPluginsInitState = HostsPluginState & - NetworkPluginState & - TimelinePluginState & - EndpointAlertsPluginState & - EndpointHostsPluginState & - ManagementPluginState; +import { SecuritySubPlugins } from '../../app/types'; +import { ManagementPluginReducer } from '../../management'; +import { EndpointAlertsPluginReducer } from '../../endpoint_alerts'; +import { EndpointHostsPluginReducer } from '../../endpoint_hosts'; +import { State } from './types'; +import { AppAction } from './actions'; export type SubPluginsInitReducer = HostsPluginReducer & NetworkPluginReducer & @@ -53,14 +28,28 @@ export type SubPluginsInitReducer = HostsPluginReducer & EndpointHostsPluginReducer & ManagementPluginReducer; -export const createInitialState = (pluginsInitState: SubPluginsInitState): State => ({ - ...initialState, - ...pluginsInitState, - inputs: createInitialInputsState(), -}); +/** + * Factory for the 'initialState' that is used to preload state into the Security App's redux store. + */ +export const createInitialState = ( + pluginsInitState: SecuritySubPlugins['store']['initialState'] +): PreloadedState => { + const preloadedState: PreloadedState = { + app: initialAppState, + dragAndDrop: initialDragAndDropState, + ...pluginsInitState, + inputs: createInitialInputsState(), + }; + return preloadedState; +}; -export const createReducer = (pluginsReducer: SubPluginsInitReducer) => - combineReducers({ +/** + * Factory for the Security app's redux reducer. + */ +export const createReducer: ( + pluginsReducer: SubPluginsInitReducer +) => Reducer = (pluginsReducer: SubPluginsInitReducer) => + combineReducers({ app: appReducer, dragAndDrop: dragAndDropReducer, inputs: inputsReducer, diff --git a/x-pack/plugins/siem/public/common/store/store.ts b/x-pack/plugins/siem/public/common/store/store.ts index 10ea61828ed36..276dcdcaedb85 100644 --- a/x-pack/plugins/siem/public/common/store/store.ts +++ b/x-pack/plugins/siem/public/common/store/store.ts @@ -12,6 +12,7 @@ import { Store, Middleware, Dispatch, + PreloadedState, } from 'redux'; import { createEpicMiddleware } from 'redux-observable'; @@ -21,11 +22,12 @@ import { telemetryMiddleware } from '../lib/telemetry'; import { appSelectors } from './app'; import { timelineSelectors } from '../../timelines/store/timeline'; import { inputsSelectors } from './inputs'; -import { State, SubPluginsInitReducer, createReducer } from './reducer'; +import { SubPluginsInitReducer, createReducer } from './reducer'; import { createRootEpic } from './epic'; import { AppApolloClient } from '../lib/lib'; import { AppAction } from './actions'; import { Immutable } from '../../../common/endpoint/types'; +import { State } from './types'; type ComposeType = typeof compose; declare global { @@ -33,10 +35,17 @@ declare global { __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: ComposeType; } } +/** + * The Redux store type for the Security app. + */ +export type SecurityAppStore = Store; let store: Store | null = null; -export { SubPluginsInitReducer }; + +/** + * Factory for Security App's redux store. + */ export const createStore = ( - state: State, + state: PreloadedState, pluginsReducer: SubPluginsInitReducer, apolloClient: Observable, additionalMiddleware?: Array>>> diff --git a/x-pack/plugins/siem/public/common/store/test_utils.ts b/x-pack/plugins/siem/public/common/store/test_utils.ts index 511cdcb6f0672..89558c37909f7 100644 --- a/x-pack/plugins/siem/public/common/store/test_utils.ts +++ b/x-pack/plugins/siem/public/common/store/test_utils.ts @@ -5,9 +5,8 @@ */ import { Dispatch } from 'redux'; -import { State } from './reducer'; +import { State, ImmutableMiddlewareFactory } from './types'; import { AppAction } from './actions'; -import { ImmutableMiddlewareFactory } from './types'; /** * Utilities for testing Redux middleware diff --git a/x-pack/plugins/siem/public/common/store/types.ts b/x-pack/plugins/siem/public/common/store/types.ts index a4bfdeb30b438..b9942979beb1e 100644 --- a/x-pack/plugins/siem/public/common/store/types.ts +++ b/x-pack/plugins/siem/public/common/store/types.ts @@ -4,19 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Dispatch, - Action as ReduxAction, - AnyAction as ReduxAnyAction, - Action, - Middleware, -} from 'redux'; +import { Dispatch, Action, Middleware, CombinedState } from 'redux'; import { CoreStart } from '../../../../../../src/core/public'; -import { Immutable } from '../../../common/endpoint_alerts/types'; -import { State } from './reducer'; import { StartPlugins } from '../../types'; import { AppAction } from './actions'; +import { Immutable } from '../../../common/endpoint/types'; +import { AppState } from './app/reducer'; +import { InputsState } from './inputs/reducer'; +import { HostsPluginState } from '../../hosts/store'; +import { DragAndDropState } from './drag_and_drop/reducer'; +import { TimelinePluginState } from '../../timelines/store/timeline'; +import { NetworkPluginState } from '../../network/store'; +import { EndpointAlertsPluginState } from '../../endpoint_alerts'; +import { EndpointHostsPluginState } from '../../endpoint_hosts'; +import { ManagementPluginState } from '../../management'; + +/** + * The redux `State` type for the Security App. + * We use `CombinedState` to wrap our shape because we create our reducer using `combineReducers`. + * `combineReducers` returns a type wrapped in `CombinedState`. + * `CombinedState` is required for redux to know what keys to make optional when preloaded state into a store. + */ +export type State = CombinedState< + HostsPluginState & + NetworkPluginState & + TimelinePluginState & + EndpointAlertsPluginState & + EndpointHostsPluginState & + ManagementPluginState & { + app: AppState; + dragAndDrop: DragAndDropState; + inputs: InputsState; + } +>; export type KueryFilterQueryKind = 'kuery' | 'lucene'; @@ -67,60 +88,73 @@ export type ImmutableMiddlewareFactory = ( * Middleware will be of the `ImmutableMiddleware` variety. Not able to directly * change actions or state. */ -export type ImmutableMultipleMiddlewareFactory = ( +export type SecuritySubPluginMiddlewareFactory = ( coreStart: CoreStart, depsStart: Pick -) => Array>; +) => Array>>>; /** - * Simple type for a redux selector. + * Like `Reducer` from `redux` but it accepts immutable versions of `state` and `action`. + * Use this type for all Reducers in order to help enforce our pattern of immutable state. */ -type Selector = (state: S) => R; +export type ImmutableReducer = ( + state: Immutable | undefined, + action: Immutable
+) => S | Immutable; /** - * Takes a selector and an `ImmutableMiddleware`. The - * middleware's version of `getState` will receive - * the result of the selector instead of the global state. - * - * This allows middleware to have knowledge of only a subsection of state. - * - * `selector` returns an `Immutable` version of the substate. - * `middleware` must be an `ImmutableMiddleware`. - * - * Returns a regular middleware, meant to be used with `applyMiddleware`. + * A alternate interface for `redux`'s `combineReducers`. Will work with the same underlying implementation, + * but will enforce that `Immutable` versions of `state` and `action` are received. */ -export type SubstateMiddlewareFactory = ( - selector: Selector>, - middleware: ImmutableMiddleware -) => Middleware<{}, State, Dispatch>>; +export type ImmutableCombineReducers = >( + reducers: M +) => ImmutableReducer< + CombinedState>, + ActionFromImmutableReducersMapObject +>; /** - * Like `Reducer` from `redux` but it accepts immutable versions of `state` and `action`. - * Use this type for all Reducers in order to help enforce our pattern of immutable state. + * Helper type for `ImmutableCombineReducers`. Infers the combined state type from an immutable reducer map. */ -export type ImmutableReducer = ( - state: Immutable | undefined, - action: Immutable -) => State | Immutable; +type StateFromImmutableReducersMapObject = M extends ImmutableReducersMapObject + ? { [P in keyof M]: M[P] extends ImmutableReducer ? S : never } + : never; /** - * A alternate interface for `redux`'s `combineReducers`. Will work with the same underlying implementation, - * but will enforce that `Immutable` versions of `state` and `action` are received. + * Helper type for `ImmutableCombineReducers`. Infers the combined action type from an immutable reducer map. + */ +type ActionFromImmutableReducersMapObject = M extends ImmutableReducersMapObject + ? ActionFromImmutableReducer> + : never; + +/** + * Helper type for `ImmutableCombineReducers`. Infers the combined reducer type from an immutable reducer map. + */ +type ImmutableReducerFromImmutableReducersMapObject = M extends { + [P in keyof M]: infer R; +} + ? R extends ImmutableReducer + ? R + : never + : never; + +/** + * Helper type for `ImmutableCombineReducers`. Infers the action type for an immutable reducer. */ -export type ImmutableCombineReducers = ( - reducers: ImmutableReducersMapObject -) => ImmutableReducer; +type ActionFromImmutableReducer = R extends ImmutableReducer ? A : never; /** + * Helper type for `ImmutableCombineReducers`. * Like `redux`'s `ReducersMapObject` (which is used by `combineReducers`) but enforces that * the `state` and `action` received are `Immutable` versions. */ -type ImmutableReducersMapObject = { +type ImmutableReducersMapObject = { [K in keyof S]: ImmutableReducer; }; /** * A better type for createStructuredSelector. This doesn't support the options object. + * https://github.com/reduxjs/reselect/pull/454 */ export type CreateStructuredSelector = < SelectorMap extends { [key: string]: (...args: never[]) => unknown } diff --git a/x-pack/plugins/siem/public/common/utils/clone_http_fetch_query.ts b/x-pack/plugins/siem/public/common/utils/clone_http_fetch_query.ts index bfa433dc9f9ac..90b81df8bc21e 100644 --- a/x-pack/plugins/siem/public/common/utils/clone_http_fetch_query.ts +++ b/x-pack/plugins/siem/public/common/utils/clone_http_fetch_query.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Immutable } from '../../../common/endpoint_alerts/types'; - import { HttpFetchQuery } from '../../../../../../src/core/public'; +import { Immutable } from '../../../common/endpoint/types'; export function cloneHttpFetchQuery(query: Immutable): HttpFetchQuery { const clone: HttpFetchQuery = {}; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/index.ts b/x-pack/plugins/siem/public/endpoint_alerts/index.ts index 6380edbde6958..e7e45b95ceb91 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/index.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/index.ts @@ -4,15 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Reducer } from 'redux'; import { SecuritySubPluginWithStore } from '../app/types'; -import { getEndpointAlertsRoutes } from './routes'; -import { Immutable } from '../../common/endpoint/types'; -import { initialAlertListState, alertListReducer } from './store/reducer'; +import { endpointAlertsRoutes } from './routes'; +import { alertListReducer } from './store/reducer'; import { AlertListState } from '../../common/endpoint_alerts/types'; import { alertMiddlewareFactory } from './store/middleware'; import { substateMiddlewareFactory } from '../common/store'; import { CoreStart } from '../../../../../src/core/public'; import { StartPlugins } from '../types'; +import { AppAction } from '../common/store/actions'; + +/** + * Internally, our state is sometimes immutable, ignore that in our external + * interface. + */ +export interface EndpointAlertsPluginState { + alertList: AlertListState; +} + +/** + * Internally, we use `ImmutableReducer`, but we present a regular reducer + * externally for compatibility w/ regular redux. + */ +export interface EndpointAlertsPluginReducer { + alertList: Reducer; +} export class EndpointAlerts { public setup() {} @@ -20,20 +37,24 @@ export class EndpointAlerts { public start( core: CoreStart, plugins: StartPlugins - ): SecuritySubPluginWithStore<'alertList', Immutable> { + ): SecuritySubPluginWithStore<'alertList', AlertListState> { const { data, ingestManager } = plugins; const middleware = [ - substateMiddlewareFactory( + substateMiddlewareFactory( (globalState) => globalState.alertList, alertMiddlewareFactory(core, { data, ingestManager }) ), ]; return { - routes: getEndpointAlertsRoutes(), + routes: endpointAlertsRoutes(), store: { - initialState: { alertList: initialAlertListState() }, - reducer: { alertList: alertListReducer }, + initialState: { alertList: undefined }, + /** + * Cast the ImmutableReducer to a regular reducer for compatibility with + * the subplugin architecture (which expects plain redux reducers.) + */ + reducer: { alertList: alertListReducer } as EndpointAlertsPluginReducer, middleware, }, }; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/models/index_pattern.ts b/x-pack/plugins/siem/public/endpoint_alerts/models/index_pattern.ts index 8daaa3fef7a29..3eb347c6cada8 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/models/index_pattern.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/models/index_pattern.ts @@ -6,7 +6,7 @@ import { all } from 'deepmerge'; import { IIndexPattern } from 'src/plugins/data/public'; -import { Immutable } from '../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../common/endpoint/types'; /** * Model for the `IIndexPattern` interface exported by the `data` plugin. diff --git a/x-pack/plugins/siem/public/endpoint_alerts/routes.tsx b/x-pack/plugins/siem/public/endpoint_alerts/routes.tsx index 60df7f5d47129..d62ef20c384dc 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/routes.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/routes.tsx @@ -9,7 +9,7 @@ import { Route } from 'react-router-dom'; import { AlertIndex } from './view'; -export const getEndpointAlertsRoutes = () => [ +export const endpointAlertsRoutes = () => [ , diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/action.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/action.ts index ae103edaa5a2b..3330cef1816a7 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/action.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/action.ts @@ -5,8 +5,8 @@ */ import { IIndexPattern } from 'src/plugins/data/public'; -// import { Immutable } from '../../../common/types'; -import { AlertDetails, AlertListData, Immutable } from '../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../common/endpoint/types'; +import { AlertDetails, AlertListData } from '../../../common/endpoint_alerts/types'; interface ServerReturnedAlertsData { readonly type: 'serverReturnedAlertsData'; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/alert_details.test.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/alert_details.test.ts index b634e6455d971..8e20ad089b9c2 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/alert_details.test.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/alert_details.test.ts @@ -8,7 +8,7 @@ import { Store, createStore, applyMiddleware } from 'redux'; import { createBrowserHistory, History } from 'history'; import { coreMock } from '../../../../../../src/core/public/mocks'; -import { AlertListState, Immutable } from '../../../common/endpoint_alerts/types'; +import { AlertListState } from '../../../common/endpoint_alerts/types'; import { depsStartMock, DepsStartMock } from '../../common/mock/endpoint'; import { alertListReducer } from './reducer'; @@ -16,6 +16,7 @@ import { alertListReducer } from './reducer'; import { alertMiddlewareFactory } from './middleware'; import { mockAlertResultList } from './mock_alert_result_list'; +import { Immutable } from '../../../common/endpoint/types'; describe('alert details tests', () => { let store: Store; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list.test.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list.test.ts index 8f82c2522ea69..a21e449552960 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list.test.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list.test.ts @@ -7,12 +7,13 @@ import { Store, createStore, applyMiddleware } from 'redux'; import { History, createBrowserHistory } from 'history'; import { alertListReducer } from './reducer'; -import { AlertListState, AlertResultList, Immutable } from '../../../common/endpoint_alerts/types'; +import { AlertListState, AlertResultList } from '../../../common/endpoint_alerts/types'; import { alertMiddlewareFactory } from './middleware'; import { coreMock } from 'src/core/public/mocks'; import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint'; import { isOnAlertPage } from './selectors'; import { mockAlertResultList } from './mock_alert_result_list'; +import { Immutable } from '../../../common/endpoint/types'; describe('alert list tests', () => { let store: Store; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/index.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/index.ts deleted file mode 100644 index dd97d60c532b0..0000000000000 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { AlertListState, Immutable } from '../../../common/endpoint_alerts/types'; -import { ImmutableReducer } from '../../common/store'; -import { AppAction } from '../../common/store/actions'; - -export { alertListReducer } from './reducer'; -export { AlertAction } from './action'; - -export interface EndpointAlertsPluginState { - alertList: Immutable; -} - -export interface EndpointAlertsPluginReducer { - alertList: ImmutableReducer; -} diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts index b8e8d36801d48..dd84b4fcff5bd 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts @@ -20,9 +20,8 @@ import { uiQueryParams, isAlertPageTabChange, } from './selectors'; -import { Immutable } from '../../../common/endpoint/types'; -export const alertMiddlewareFactory: ImmutableMiddlewareFactory> = ( +export const alertMiddlewareFactory: ImmutableMiddlewareFactory = ( coreStart, depsStart ) => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/reducer.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/reducer.ts index 3e79ad4d1c614..22fc5025656d7 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/reducer.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/reducer.ts @@ -4,26 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Immutable, AlertListState } from '../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../common/endpoint/types'; +import { AlertListState } from '../../../common/endpoint_alerts/types'; import { ImmutableReducer } from '../../common/store'; import { AppAction } from '../../common/store/actions'; -export const initialAlertListState = (): Immutable => { - return { - alerts: [], - alertDetails: undefined, - pageSize: 10, - pageIndex: 0, - total: 0, - location: undefined, - searchBar: { - patterns: [], - }, - }; +export const initialAlertListState: Immutable = { + alerts: [], + alertDetails: undefined, + pageSize: 10, + pageIndex: 0, + total: 0, + location: undefined, + searchBar: { + patterns: [], + }, }; export const alertListReducer: ImmutableReducer = ( - state = initialAlertListState(), + state = initialAlertListState, action ) => { if (action.type === 'serverReturnedAlertsData') { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts index bec524f948d76..ab0e4165a2577 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts @@ -12,10 +12,10 @@ import { } from 'reselect'; import { encode, decode } from 'rison-node'; +import { Immutable } from '../../../common/endpoint/types'; import { Query, TimeRange, Filter } from '../../../../../../src/plugins/data/public'; import { - Immutable, AlertingIndexGetQueryInput, AlertListState, AlertingIndexUIQueryParams, diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx index a4fe4811fa602..de939ad4f54c6 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx @@ -11,7 +11,7 @@ import { Store } from 'redux'; import { mockAlertDetailsResult } from '../store/mock_alert_result_list'; import { alertPageTestRender } from './test_helpers/render_alert_page'; import { AppAction } from '../../common/store/actions'; -import { State } from '../../common/store/reducer'; +import { State } from '../../common/store/types'; describe('when the alert details flyout is open', () => { let render: () => reactTestingLibrary.RenderResult; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/file_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/file_accordion.tsx index 1009bec0cec0e..e319cbe52a69c 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/file_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/file_accordion.tsx @@ -6,7 +6,8 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; -import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AlertData } from '../../../../../common/endpoint_alerts/types'; import { FormattedDate } from '../../formatted_date'; export const FileAccordion = memo(({ alertData }: { alertData: Immutable }) => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/general_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/general_accordion.tsx index fc0d38188fd27..76ee46cb5a107 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/general_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/general_accordion.tsx @@ -6,8 +6,9 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; -import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types'; +import { AlertData } from '../../../../../common/endpoint_alerts/types'; import { FormattedDate } from '../../formatted_date'; +import { Immutable } from '../../../../../common/endpoint/types'; export const GeneralAccordion = memo(({ alertData }: { alertData: Immutable }) => { const columns = useMemo(() => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/hash_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/hash_accordion.tsx index ae62bd80b73bc..3077a98905dff 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/hash_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/hash_accordion.tsx @@ -6,7 +6,8 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; -import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AlertData } from '../../../../../common/endpoint_alerts/types'; export const HashAccordion = memo(({ alertData }: { alertData: Immutable }) => { const columns = useMemo(() => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/host_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/host_accordion.tsx index 70723efd97b8c..bea49cbba06ba 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/host_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/host_accordion.tsx @@ -8,7 +8,8 @@ import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList, EuiHealth } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Immutable, AlertDetails } from '../../../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AlertDetails } from '../../../../../common/endpoint_alerts/types'; export const HostAccordion = memo(({ alertData }: { alertData: Immutable }) => { const columns = useMemo(() => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_accordion.tsx index 607327a49de1c..b2d0e369bc453 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_accordion.tsx @@ -6,7 +6,8 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; -import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AlertData } from '../../../../../common/endpoint_alerts/types'; export const SourceProcessAccordion = memo(({ alertData }: { alertData: Immutable }) => { const columns = useMemo(() => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_token_accordion.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_token_accordion.tsx index 9be494d92a88d..e559daa0a6350 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_token_accordion.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/metadata/source_process_token_accordion.tsx @@ -6,7 +6,8 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; -import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AlertData } from '../../../../../common/endpoint_alerts/types'; export const SourceProcessTokenAccordion = memo( ({ alertData }: { alertData: Immutable }) => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/hooks/use_alerts_selector.ts b/x-pack/plugins/siem/public/endpoint_alerts/view/hooks/use_alerts_selector.ts index 726f6a453cb5d..95c347893b99a 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/hooks/use_alerts_selector.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/hooks/use_alerts_selector.ts @@ -5,8 +5,9 @@ */ import { useSelector } from 'react-redux'; -import { Immutable, AlertListState } from '../../../../common/endpoint_alerts/types'; -import { State } from '../../../common/store/reducer'; +import { Immutable } from '../../../../common/endpoint/types'; +import { AlertListState } from '../../../../common/endpoint_alerts/types'; +import { State } from '../../../common/store/types'; export function useAlertListSelector( selector: ( diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx index 4967d7661a085..3d056ca5c188f 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx @@ -12,7 +12,7 @@ import { Store } from 'redux'; import { mockAlertResultList } from '../store/mock_alert_result_list'; import { alertPageTestRender } from './test_helpers/render_alert_page'; import { DepsStartMock } from '../../common/mock/endpoint'; -import { State } from '../../common/store/reducer'; +import { State } from '../../common/store/types'; import { AppAction } from '../../common/store/actions'; describe('when on the alerting page', () => { diff --git a/x-pack/plugins/siem/public/endpoint_hosts/index.ts b/x-pack/plugins/siem/public/endpoint_hosts/index.ts index 1c2649ec5cf91..bd1c5f96f8cda 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/index.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/index.ts @@ -4,15 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Reducer } from 'redux'; import { SecuritySubPluginWithStore } from '../app/types'; -import { getEndpointHostsRoutes } from './routes'; -import { initialHostListState, hostListReducer } from './store/reducer'; -import { Immutable } from '../../common/endpoint/types'; +import { endpointHostsRoutes } from './routes'; +import { hostListReducer } from './store/reducer'; import { HostState } from './types'; import { hostMiddlewareFactory } from './store/middleware'; import { CoreStart } from '../../../../../src/core/public'; import { StartPlugins } from '../types'; import { substateMiddlewareFactory } from '../common/store'; +import { AppAction } from '../common/store/actions'; + +/** + * Internally, our state is sometimes immutable, ignore that in our external + * interface. + */ +export interface EndpointHostsPluginState { + hostList: HostState; +} + +/** + * Internally, we use `ImmutableReducer`, but we present a regular reducer + * externally for compatibility w/ regular redux. + */ +export interface EndpointHostsPluginReducer { + hostList: Reducer; +} export class EndpointHosts { public setup() {} @@ -20,7 +37,7 @@ export class EndpointHosts { public start( core: CoreStart, plugins: StartPlugins - ): SecuritySubPluginWithStore<'hostList', Immutable> { + ): SecuritySubPluginWithStore<'hostList', HostState> { const { data, ingestManager } = plugins; const middleware = [ substateMiddlewareFactory( @@ -29,10 +46,14 @@ export class EndpointHosts { ), ]; return { - routes: getEndpointHostsRoutes(), + routes: endpointHostsRoutes(), store: { - initialState: { hostList: initialHostListState() }, - reducer: { hostList: hostListReducer }, + initialState: { hostList: undefined }, + /** + * Cast the ImmutableReducer to a regular reducer for compatibility with + * the subplugin architecture (which expects plain redux reducers.) + */ + reducer: { hostList: hostListReducer } as EndpointHostsPluginReducer, middleware, }, }; diff --git a/x-pack/plugins/siem/public/endpoint_hosts/routes.tsx b/x-pack/plugins/siem/public/endpoint_hosts/routes.tsx index b7e549dc4e5e8..4ff9ecfaeab0e 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/routes.tsx +++ b/x-pack/plugins/siem/public/endpoint_hosts/routes.tsx @@ -9,7 +9,7 @@ import { Route } from 'react-router-dom'; import { HostList } from './view'; -export const getEndpointHostsRoutes = () => [ +export const endpointHostsRoutes = () => [ , diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/index.test.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/index.test.ts index 8518c37fe3f5d..71452993abf07 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/index.test.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/store/index.test.ts @@ -5,10 +5,11 @@ */ import { createStore, Dispatch, Store } from 'redux'; -import { HostAction, hostListReducer } from './index'; import { HostState } from '../types'; import { listData } from './selectors'; import { mockHostResultList } from './mock_host_result_list'; +import { HostAction } from './action'; +import { hostListReducer } from './reducer'; describe('HostList store concerns', () => { let store: Store; diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/index.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/index.ts deleted file mode 100644 index eafea5b9c7404..0000000000000 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { HostState } from '../types'; -import { ImmutableReducer } from '../../common/store'; -import { AppAction } from '../../common/store/actions'; -import { Immutable } from '../../../common/endpoint/types'; - -export { hostListReducer } from './reducer'; -export { HostAction } from './action'; -export { hostMiddlewareFactory } from './middleware'; - -export interface EndpointHostsPluginState { - hostList: Immutable; -} - -export interface EndpointHostsPluginReducer { - hostList: ImmutableReducer; -} diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.test.ts index 6c9e3dd41907f..0959a3438aad9 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.test.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.test.ts @@ -7,7 +7,6 @@ import { CoreStart, HttpSetup } from 'kibana/public'; import { applyMiddleware, createStore, Store } from 'redux'; import { coreMock } from '../../../../../../src/core/public/mocks'; import { History, createBrowserHistory } from 'history'; -import { hostListReducer, hostMiddlewareFactory } from './index'; import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint'; @@ -17,6 +16,8 @@ import { AppAction } from '../../common/store/actions'; import { mockHostResultList } from './mock_host_result_list'; import { listData } from './selectors'; import { HostState } from '../types'; +import { hostListReducer } from './reducer'; +import { hostMiddlewareFactory } from './middleware'; describe('host list middleware', () => { let fakeCoreStart: jest.Mocked; diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.ts index b9d8475601023..dd9ab19a702ea 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/store/middleware.ts @@ -4,14 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostResultList, Immutable } from '../../../common/endpoint/types'; +import { HostResultList } from '../../../common/endpoint/types'; import { ImmutableMiddlewareFactory } from '../../common/store'; import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors'; import { HostState } from '../types'; -export const hostMiddlewareFactory: ImmutableMiddlewareFactory> = ( - coreStart -) => { +export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (coreStart) => { return ({ getState, dispatch }) => (next) => async (action) => { next(action); const state = getState(); diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/reducer.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/reducer.ts index 98f4a457a4598..c0d5e6931db2b 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/store/reducer.ts @@ -10,26 +10,24 @@ import { AppAction } from '../../common/store/actions'; import { ImmutableReducer } from '../../common/store'; import { Immutable } from '../../../common/endpoint/types'; -export const initialHostListState = (): HostState => { - return { - hosts: [], - pageSize: 10, - pageIndex: 0, - total: 0, - loading: false, - error: undefined, - details: undefined, - detailsLoading: false, - detailsError: undefined, - policyResponse: undefined, - policyResponseLoading: false, - policyResponseError: undefined, - location: undefined, - }; +export const initialHostListState: Immutable = { + hosts: [], + pageSize: 10, + pageIndex: 0, + total: 0, + loading: false, + error: undefined, + details: undefined, + detailsLoading: false, + detailsError: undefined, + policyResponse: undefined, + policyResponseLoading: false, + policyResponseError: undefined, + location: undefined, }; export const hostListReducer: ImmutableReducer = ( - state = initialHostListState(), + state = initialHostListState, action ) => { if (action.type === 'serverReturnedHostList') { diff --git a/x-pack/plugins/siem/public/endpoint_hosts/view/hooks.ts b/x-pack/plugins/siem/public/endpoint_hosts/view/hooks.ts index 6d552831c2fec..78fd679f818b6 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/view/hooks.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/view/hooks.ts @@ -8,7 +8,7 @@ import { useSelector } from 'react-redux'; import { useMemo } from 'react'; import { HostState } from '../types'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { State } from '../../common/store/reducer'; +import { State } from '../../common/store/types'; export function useHostSelector(selector: (state: HostState) => TSelected) { return useSelector(function (state: State) { diff --git a/x-pack/plugins/siem/public/graphql/types.ts b/x-pack/plugins/siem/public/graphql/types.ts index eae3887ec0636..628d56ebb647c 100644 --- a/x-pack/plugins/siem/public/graphql/types.ts +++ b/x-pack/plugins/siem/public/graphql/types.ts @@ -5405,6 +5405,8 @@ export namespace GetOneTimeline { pinnedEventsSaveObject: Maybe; + status: Maybe; + title: Maybe; timelineType: Maybe; diff --git a/x-pack/plugins/siem/public/hosts/store/selectors.ts b/x-pack/plugins/siem/public/hosts/store/selectors.ts index aee93441e9409..61fa44a5f41d1 100644 --- a/x-pack/plugins/siem/public/hosts/store/selectors.ts +++ b/x-pack/plugins/siem/public/hosts/store/selectors.ts @@ -7,7 +7,7 @@ import { get } from 'lodash/fp'; import { createSelector } from 'reselect'; -import { State } from '../../common/store/reducer'; +import { State } from '../../common/store/types'; import { GenericHostsModel, HostsType, HostsTableType } from './model'; diff --git a/x-pack/plugins/siem/public/management/index.ts b/x-pack/plugins/siem/public/management/index.ts index 86522df110dfb..d6a723e5340bf 100644 --- a/x-pack/plugins/siem/public/management/index.ts +++ b/x-pack/plugins/siem/public/management/index.ts @@ -5,33 +5,53 @@ */ import { CoreStart } from 'kibana/public'; -import { managementReducer, getManagementInitialState, managementMiddlewareFactory } from './store'; -import { getManagementRoutes } from './routes'; +import { Reducer, CombinedState } from 'redux'; +import { managementRoutes } from './routes'; import { StartPlugins } from '../types'; -import { MANAGEMENT_STORE_GLOBAL_NAMESPACE } from './common/constants'; import { SecuritySubPluginWithStore } from '../app/types'; -import { Immutable } from '../../common/endpoint/types'; -import { ManagementStoreGlobalNamespace } from './types'; -import { ManagementState } from './store/types'; +import { managementReducer } from './store/reducer'; +import { AppAction } from '../common/store/actions'; +import { managementMiddlewareFactory } from './store/middleware'; +import { ManagementState } from './types'; export { getManagementUrl } from './common/routing'; +/** + * Internally, our state is sometimes immutable, ignore that in our external + * interface. + */ +export interface ManagementPluginState { + management: ManagementState; +} + +/** + * Internally, we use `ImmutableReducer`, but we present a regular reducer + * externally for compatibility w/ regular redux. + */ +export interface ManagementPluginReducer { + management: Reducer, AppAction>; +} + export class Management { public setup() {} public start( core: CoreStart, plugins: StartPlugins - ): SecuritySubPluginWithStore> { + ): SecuritySubPluginWithStore<'management', ManagementState> { return { - routes: getManagementRoutes(), + routes: managementRoutes(), store: { initialState: { - [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: getManagementInitialState(), + management: undefined, }, + /** + * Cast the ImmutableReducer to a regular reducer for compatibility with + * the subplugin architecture (which expects plain redux reducers.) + */ reducer: { - [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: managementReducer, - }, + management: managementReducer, + } as ManagementPluginReducer, middleware: managementMiddlewareFactory(core, plugins), }, }; diff --git a/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/middleware.ts b/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/middleware.ts index 97cdcac0fcae9..ec0c526482b45 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/middleware.ts +++ b/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/middleware.ts @@ -16,13 +16,13 @@ import { sendGetFleetAgentStatusForConfig, sendPutDatasource, } from '../policy_list/services/ingest'; -import { NewPolicyData, PolicyData, Immutable } from '../../../../../../common/endpoint/types'; +import { NewPolicyData, PolicyData } from '../../../../../../common/endpoint/types'; import { factory as policyConfigFactory } from '../../../../../../common/endpoint/models/policy_config'; import { ImmutableMiddlewareFactory } from '../../../../../common/store'; -export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory> = (coreStart) => { +export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory = ( + coreStart +) => { const http = coreStart.http; return ({ getState, dispatch }) => (next) => async (action) => { diff --git a/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/reducer.ts b/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/reducer.ts index 95daad7ae5977..75e7808ea30b1 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/reducer.ts +++ b/x-pack/plugins/siem/public/management/pages/policy/store/policy_details/reducer.ts @@ -4,24 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ import { fullPolicy, isOnPolicyDetailsPage } from './selectors'; -import { PolicyDetailsState } from '../../types'; import { Immutable, PolicyConfig, UIPolicyConfig } from '../../../../../../common/endpoint/types'; import { ImmutableReducer } from '../../../../../common/store'; import { AppAction } from '../../../../../common/store/actions'; +import { PolicyDetailsState } from '../../types'; -export const initialPolicyDetailsState = (): PolicyDetailsState => { - return { - policyItem: undefined, - isLoading: false, - agentStatusSummary: { - error: 0, - events: 0, - offline: 0, - online: 0, - total: 0, - }, - }; -}; +/** + * Return a fresh copy of initial state, since we mutate state in the reducer. + */ +export const initialPolicyDetailsState: () => Immutable = () => ({ + policyItem: undefined, + isLoading: false, + agentStatusSummary: { + error: 0, + events: 0, + offline: 0, + online: 0, + total: 0, + }, +}); export const policyDetailsReducer: ImmutableReducer = ( state = initialPolicyDetailsState(), diff --git a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/index.test.ts b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/index.test.ts index c796edff8aabc..a312134bbcd22 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/index.test.ts +++ b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/index.test.ts @@ -10,7 +10,7 @@ import { Store, applyMiddleware, createStore } from 'redux'; import { coreMock } from '../../../../../../../../../src/core/public/mocks'; import { DATASOURCE_SAVED_OBJECT_TYPE } from '../../../../../../../ingest_manager/common'; -import { policyListReducer, initialPolicyListState } from './reducer'; +import { policyListReducer } from './reducer'; import { policyListMiddlewareFactory } from './middleware'; import { isOnPolicyListPage, selectIsLoading, urlSearchParams } from './selectors'; @@ -39,7 +39,7 @@ describe('policy list store concerns', () => { store = createStore( policyListReducer, - initialPolicyListState(), + undefined, applyMiddleware(policyListMiddlewareFactory(fakeCoreStart, depsStart), actionSpyMiddleware) ); }); diff --git a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts index 6054ec34b2d01..66962c378537f 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts +++ b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/middleware.ts @@ -8,9 +8,9 @@ import { GetPolicyListResponse, PolicyListState } from '../../types'; import { sendGetEndpointSpecificDatasources } from './services/ingest'; import { isOnPolicyListPage, urlSearchParams } from './selectors'; import { ImmutableMiddlewareFactory } from '../../../../../common/store'; -import { Immutable } from '../../../../../../common/endpoint/types'; +import { initialPolicyListState } from './reducer'; -export const policyListMiddlewareFactory: ImmutableMiddlewareFactory> = ( +export const policyListMiddlewareFactory: ImmutableMiddlewareFactory = ( coreStart ) => { const http = coreStart.http; @@ -38,15 +38,13 @@ export const policyListMiddlewareFactory: ImmutableMiddlewareFactory GetPolicyListResponse = (options = {}) => { + const { + total = 1, + request_page_size: requestPageSize = 10, + request_page_index: requestPageIndex = 0, + } = options; + + // Skip any that are before the page we're on + const numberToSkip = requestPageSize * requestPageIndex; + + // total - numberToSkip is the count of non-skipped ones, but return no more than a pageSize, and no less than 0 + const actualCountToReturn = Math.max(Math.min(total - numberToSkip, requestPageSize), 0); + + const policies = []; + for (let index = 0; index < actualCountToReturn; index++) { + const generator = new EndpointDocGenerator('seed'); + policies.push(generator.generatePolicyDatasource()); + } + const mock: GetPolicyListResponse = { + items: policies, + total, + page: requestPageIndex, + perPage: requestPageSize, + success: true, + }; + return mock; +}; diff --git a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/reducer.ts b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/reducer.ts index 028e46936b293..3e5c70e53ae9b 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/reducer.ts +++ b/x-pack/plugins/siem/public/management/pages/policy/store/policy_list/reducer.ts @@ -4,23 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PolicyListState } from '../../types'; import { isOnPolicyListPage } from './selectors'; import { ImmutableReducer } from '../../../../../common/store'; import { AppAction } from '../../../../../common/store/actions'; import { Immutable } from '../../../../../../common/endpoint/types'; +import { PolicyListState } from '../../types'; -export const initialPolicyListState = (): PolicyListState => { - return { - policyItems: [], - isLoading: false, - apiError: undefined, - pageIndex: 0, - pageSize: 10, - total: 0, - location: undefined, - }; -}; +/** + * Return the initial state. + * In case `state` was mutated, we return a fresh initial state object. + */ +export const initialPolicyListState: () => Immutable = () => ({ + policyItems: [], + isLoading: false, + apiError: undefined, + pageIndex: 0, + pageSize: 10, + total: 0, + location: undefined, +}); export const policyListReducer: ImmutableReducer = ( state = initialPolicyListState(), diff --git a/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.test.tsx b/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.test.tsx new file mode 100644 index 0000000000000..a2901ab6bfe5c --- /dev/null +++ b/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.test.tsx @@ -0,0 +1,65 @@ +/* + * 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 * as reactTestingLibrary from '@testing-library/react'; + +import { PolicyList } from './index'; +import { mockPolicyResultList } from '../store/policy_list/mock_policy_result_list'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; +import { AppAction } from '../../../../common/store/actions'; + +describe('when on the policies page', () => { + let render: () => ReturnType; + let history: AppContextTestRender['history']; + let store: AppContextTestRender['store']; + + beforeEach(() => { + const mockedContext = createAppRootMockRenderer(); + ({ history, store } = mockedContext); + render = () => mockedContext.render(); + }); + + it('should show a table', async () => { + const renderResult = render(); + const table = await renderResult.findByTestId('policyTable'); + expect(table).not.toBeNull(); + }); + + describe('when list data loads', () => { + let firstPolicyID: string; + beforeEach(() => { + reactTestingLibrary.act(() => { + history.push('/management/policy'); + reactTestingLibrary.act(() => { + const policyListData = mockPolicyResultList({ total: 3 }); + firstPolicyID = policyListData.items[0].id; + const action: AppAction = { + type: 'serverReturnedPolicyListData', + payload: { + policyItems: policyListData.items, + total: policyListData.total, + pageSize: policyListData.perPage, + pageIndex: policyListData.page, + }, + }; + store.dispatch(action); + }); + }); + }); + it('should display rows in the table', async () => { + const renderResult = render(); + const rows = await renderResult.findAllByRole('row'); + expect(rows).toHaveLength(4); + }); + it('should display policy name value as a link', async () => { + const renderResult = render(); + const policyNameLink = (await renderResult.findAllByTestId('policyNameLink'))[0]; + expect(policyNameLink).not.toBeNull(); + expect(policyNameLink.getAttribute('href')).toContain(`policy/${firstPolicyID}`); + }); + }); +}); diff --git a/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.tsx index 3a8004aa2ec6d..2826289dab8e2 100644 --- a/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/siem/public/management/pages/policy/view/policy_list.tsx @@ -4,20 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useEffect, useMemo } from 'react'; -import { EuiBasicTable, EuiText, EuiTableFieldDataColumnType, EuiLink } from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, CSSProperties, useState } from 'react'; +import { + EuiBasicTable, + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiTableFieldDataColumnType, + EuiLink, + EuiPopover, + EuiContextMenuPanelProps, + EuiContextMenuItem, + EuiButtonIcon, + EuiContextMenuPanel, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useDispatch } from 'react-redux'; import { useLocation, useHistory } from 'react-router-dom'; -import { - selectApiError, - selectIsLoading, - selectPageIndex, - selectPageSize, - selectPolicyItems, - selectTotal, -} from '../store/policy_list/selectors'; +import { createStructuredSelector } from 'reselect'; +import { CreateStructuredSelector } from '../../../../common/store'; +import * as selectors from '../store/policy_list/selectors'; import { usePolicyListSelector } from './policy_hooks'; import { PolicyListAction } from '../store/policy_list'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; @@ -27,11 +34,53 @@ import { LinkToApp } from '../../../../common/components/endpoint/link_to_app'; import { ManagementPageView } from '../../../components/management_page_view'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { getManagementUrl } from '../../../common/routing'; +import { FormattedDateAndTime } from '../../../../common/components/endpoint/formatted_date_time'; interface TableChangeCallbackArguments { page: { index: number; size: number }; } +interface PackageData { + name: string; + title: string; + version: string; +} + +const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}); + +// eslint-disable-next-line react/display-name +export const TableRowActions = React.memo<{ items: EuiContextMenuPanelProps['items'] }>( + ({ items }) => { + const [isOpen, setIsOpen] = useState(false); + const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); + const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); + + return ( + + } + isOpen={isOpen} + closePopover={handleCloseMenu} + > + + + ); + } +); + const PolicyLink: React.FC<{ name: string; route: string; href: string }> = ({ name, route, @@ -40,24 +89,32 @@ const PolicyLink: React.FC<{ name: string; route: string; href: string }> = ({ const clickHandler = useNavigateByRouterEventHandler(route); return ( // eslint-disable-next-line @elastic/eui/href-or-on-click - + {name} ); }; +const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); export const PolicyList = React.memo(() => { const { services, notifications } = useKibana(); const history = useHistory(); const location = useLocation(); const dispatch = useDispatch<(action: PolicyListAction) => void>(); - const policyItems = usePolicyListSelector(selectPolicyItems); - const pageIndex = usePolicyListSelector(selectPageIndex); - const pageSize = usePolicyListSelector(selectPageSize); - const totalItemCount = usePolicyListSelector(selectTotal); - const loading = usePolicyListSelector(selectIsLoading); - const apiError = usePolicyListSelector(selectApiError); + const { + selectPolicyItems: policyItems, + selectPageIndex: pageIndex, + selectPageSize: pageSize, + selectTotal: totalItemCount, + selectIsLoading: loading, + selectApiError: apiError, + } = usePolicyListSelector(selector); useEffect(() => { if (apiError) { @@ -94,58 +151,110 @@ export const PolicyList = React.memo(() => { defaultMessage: 'Policy Name', }), // eslint-disable-next-line react/display-name - render: (value: string, item: Immutable) => { + render: (name: string, item: Immutable) => { const routePath = getManagementUrl({ name: 'policyDetails', policyId: item.id, excludePrefix: true, }); const routeUrl = getManagementUrl({ name: 'policyDetails', policyId: item.id }); - return ; + return ( + + + + + + + + + + + ); }, - truncateText: true, }, { - field: 'revision', - name: i18n.translate('xpack.siem.endpoint.policyList.revisionField', { - defaultMessage: 'Revision', + field: 'created_by', + name: i18n.translate('xpack.siem.endpoint.policyList.createdBy', { + defaultMessage: 'Created By', }), - dataType: 'number', + truncateText: true, }, { - field: 'package', - name: i18n.translate('xpack.siem.endpoint.policyList.versionField', { - defaultMessage: 'Version', + field: 'created_at', + name: i18n.translate('xpack.siem.endpoint.policyList.createdAt', { + defaultMessage: 'Created Date', }), - render(pkg) { - return `${pkg.title} v${pkg.version}`; + render(createdAt: string) { + return ; }, }, { - field: 'description', - name: i18n.translate('xpack.siem.endpoint.policyList.descriptionField', { - defaultMessage: 'Description', + field: 'updated_by', + name: i18n.translate('xpack.siem.endpoint.policyList.updatedBy', { + defaultMessage: 'Last Updated By', }), truncateText: true, }, { - field: 'config_id', - name: i18n.translate('xpack.siem.endpoint.policyList.agentConfigField', { - defaultMessage: 'Agent Configuration', + field: 'updated_at', + name: i18n.translate('xpack.siem.endpoint.policyList.updatedAt', { + defaultMessage: 'Last Updated', }), - render(version: string) { - return ( - - {version} - - ); + render(updatedAt: string) { + return ; }, }, + { + field: 'package', + name: i18n.translate('xpack.siem.endpoint.policyList.versionFieldLabel', { + defaultMessage: 'Version', + }), + render(pkg: Immutable) { + return i18n.translate('xpack.siem.endpoint.policyList.versionField', { + defaultMessage: '{title} v{version}', + values: { + title: pkg.title, + version: pkg.version, + }, + }); + }, + }, + { + field: '', + name: 'Actions', + actions: [ + { + // eslint-disable-next-line react/display-name + render: (item: Immutable) => { + return ( + + + + + , + ]} + /> + ); + }, + }, + ], + }, ], [services.application] ); @@ -174,6 +283,7 @@ export const PolicyList = React.memo(() => { pagination={paginationSetup} onChange={handleTableChange} data-test-subj="policyTable" + hasActions={false} /> diff --git a/x-pack/plugins/siem/public/management/routes.tsx b/x-pack/plugins/siem/public/management/routes.tsx index fbcea37c76962..12727ea97458e 100644 --- a/x-pack/plugins/siem/public/management/routes.tsx +++ b/x-pack/plugins/siem/public/management/routes.tsx @@ -12,7 +12,7 @@ import { MANAGEMENT_ROUTING_ROOT_PATH } from './common/constants'; /** * Returns the React Router Routes for the management area */ -export const getManagementRoutes = () => [ +export const managementRoutes = () => [ // Mounts the Management interface on `/management` , ]; diff --git a/x-pack/plugins/siem/public/management/store/index.ts b/x-pack/plugins/siem/public/management/store/index.ts deleted file mode 100644 index 50049f9828082..0000000000000 --- a/x-pack/plugins/siem/public/management/store/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { managementReducer, getManagementInitialState } from './reducer'; -export { managementMiddlewareFactory } from './middleware'; diff --git a/x-pack/plugins/siem/public/management/store/middleware.ts b/x-pack/plugins/siem/public/management/store/middleware.ts index f73736e04a5b7..c8eb27e35f9dd 100644 --- a/x-pack/plugins/siem/public/management/store/middleware.ts +++ b/x-pack/plugins/siem/public/management/store/middleware.ts @@ -4,30 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ImmutableMultipleMiddlewareFactory, substateMiddlewareFactory } from '../../common/store'; +import { + substateMiddlewareFactory, + SecuritySubPluginMiddlewareFactory, + State, +} from '../../common/store'; import { policyListMiddlewareFactory } from '../pages/policy/store/policy_list'; import { policyDetailsMiddlewareFactory } from '../pages/policy/store/policy_details'; -import { - MANAGEMENT_STORE_GLOBAL_NAMESPACE, - MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, - MANAGEMENT_STORE_POLICY_LIST_NAMESPACE, -} from '../common/constants'; -// @ts-ignore -export const managementMiddlewareFactory: ImmutableMultipleMiddlewareFactory = ( +export const managementMiddlewareFactory: SecuritySubPluginMiddlewareFactory = ( coreStart, depsStart ) => { + const listSelector = (state: State) => state.management.policyList; + const detailSelector = (state: State) => state.management.policyDetails; + return [ - substateMiddlewareFactory( - (globalState) => - globalState[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_POLICY_LIST_NAMESPACE], - policyListMiddlewareFactory(coreStart, depsStart) - ), - substateMiddlewareFactory( - (globalState) => - globalState[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE], - policyDetailsMiddlewareFactory(coreStart, depsStart) - ), + substateMiddlewareFactory(listSelector, policyListMiddlewareFactory(coreStart, depsStart)), + substateMiddlewareFactory(detailSelector, policyDetailsMiddlewareFactory(coreStart, depsStart)), ]; }; diff --git a/x-pack/plugins/siem/public/management/store/reducer.ts b/x-pack/plugins/siem/public/management/store/reducer.ts index ba7927684ad3d..64b2ab5c05f96 100644 --- a/x-pack/plugins/siem/public/management/store/reducer.ts +++ b/x-pack/plugins/siem/public/management/store/reducer.ts @@ -4,42 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineReducers as reduxCombineReducers } from 'redux'; +import { combineReducers } from 'redux'; import { - initialPolicyDetailsState, policyDetailsReducer, + initialPolicyDetailsState, } from '../pages/policy/store/policy_details/reducer'; import { - initialPolicyListState, policyListReducer, + initialPolicyListState, } from '../pages/policy/store/policy_list/reducer'; import { MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, MANAGEMENT_STORE_POLICY_LIST_NAMESPACE, } from '../common/constants'; import { ImmutableCombineReducers } from '../../common/store'; -import { AppAction } from '../../common/store/actions'; -import { ManagementState } from './types'; +import { Immutable } from '../../../common/endpoint/types'; +import { ManagementState } from '../types'; -// Change the type of `combinerReducers` locally -const combineReducers: ImmutableCombineReducers = reduxCombineReducers; +const immutableCombineReducers: ImmutableCombineReducers = combineReducers; -/** - * Returns the initial state of the store for the SIEM Management section - */ -export const getManagementInitialState = (): ManagementState => { - return { - [MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: initialPolicyListState(), - [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: initialPolicyDetailsState(), - }; +export const mockManagementState: Immutable = { + policyList: initialPolicyListState(), + policyDetails: initialPolicyDetailsState(), }; /** * Redux store reducer for the SIEM Management section */ -export const managementReducer = combineReducers({ - // @ts-ignore +export const managementReducer = immutableCombineReducers({ [MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: policyListReducer, - // @ts-ignore [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: policyDetailsReducer, }); diff --git a/x-pack/plugins/siem/public/management/store/types.ts b/x-pack/plugins/siem/public/management/store/types.ts deleted file mode 100644 index 884724982fa8f..0000000000000 --- a/x-pack/plugins/siem/public/management/store/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Immutable } from '../../../common/endpoint/types'; -import { PolicyDetailsState, PolicyListState } from '../pages/policy/types'; -import { ImmutableReducer } from '../../common/store'; -import { AppAction } from '../../common/store/actions'; - -/** - * Redux store state for the Management section - */ -export interface ManagementState { - policyDetails: Immutable; - policyList: Immutable; -} - -export interface ManagementPluginState { - management: ManagementState; -} - -export interface ManagementPluginReducer { - management: ImmutableReducer; -} diff --git a/x-pack/plugins/siem/public/management/types.ts b/x-pack/plugins/siem/public/management/types.ts index 5ee16bcd434e3..eeeafb4cbe150 100644 --- a/x-pack/plugins/siem/public/management/types.ts +++ b/x-pack/plugins/siem/public/management/types.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { CombinedState } from 'redux'; import { SiemPageName } from '../app/types'; +import { PolicyListState, PolicyDetailsState } from './pages/policy/types'; /** * The type for the management store global namespace. Used mostly internally to reference @@ -12,6 +14,11 @@ import { SiemPageName } from '../app/types'; */ export type ManagementStoreGlobalNamespace = 'management'; +export type ManagementState = CombinedState<{ + policyList: PolicyListState; + policyDetails: PolicyDetailsState; +}>; + /** * The management list of sub-tabs. Changes to these will impact the Router routes. */ diff --git a/x-pack/plugins/siem/public/network/store/selectors.ts b/x-pack/plugins/siem/public/network/store/selectors.ts index eac373cd63ebd..cef8b139402ef 100644 --- a/x-pack/plugins/siem/public/network/store/selectors.ts +++ b/x-pack/plugins/siem/public/network/store/selectors.ts @@ -8,7 +8,7 @@ import { createSelector } from 'reselect'; import { get } from 'lodash/fp'; import { FlowTargetSourceDest } from '../../graphql/types'; -import { State } from '../../common/store/reducer'; +import { State } from '../../common/store/types'; import { initialNetworkState } from './reducer'; import { IpDetailsTableType, diff --git a/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx index 9ba150a82ec9a..574260a819071 100644 --- a/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx +++ b/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx @@ -11,7 +11,6 @@ import { Position } from '@elastic/charts'; import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; import { SHOWING, UNIT } from '../../../common/components/alerts_viewer/translations'; -import { getDetectionEngineAlertUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; import { MatrixHistogramContainer } from '../../../common/components/matrix_histogram'; import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; @@ -22,7 +21,7 @@ import { Query, } from '../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../../common/store'; -import { HostsType } from '../../../hosts/store/model'; +import { HostsTableType, HostsType } from '../../../hosts/store/model'; import * as i18n from '../../pages/translations'; import { @@ -32,6 +31,7 @@ import { import { MatrixHisrogramConfigs } from '../../../common/components/matrix_histogram/types'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; import { navTabs } from '../../../app/home/home_navigations'; +import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts'; const ID = 'alertsByCategoryOverview'; @@ -75,11 +75,14 @@ const AlertsByCategoryComponent: React.FC = ({ const kibana = useKibana(); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - const urlSearch = useGetUrlSearch(navTabs.detections); + const urlSearch = useGetUrlSearch(navTabs.hosts); const alertsCountViewAlertsButton = useMemo( () => ( - + {i18n.VIEW_ALERTS} ), diff --git a/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx b/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx index d569fd61afc9c..f2ad45be93522 100644 --- a/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx +++ b/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx @@ -6,9 +6,9 @@ import React, { useCallback } from 'react'; -import { SignalsHistogramPanel } from '../../../alerts/components/signals_histogram_panel'; -import { signalsHistogramOptions } from '../../../alerts/components/signals_histogram_panel/config'; -import { useSignalIndex } from '../../../alerts/containers/detection_engine/signals/use_signal_index'; +import { AlertsHistogramPanel } from '../../../alerts/components/alerts_histogram_panel'; +import { alertsHistogramOptions } from '../../../alerts/components/alerts_histogram_panel/config'; +import { useSignalIndex } from '../../../alerts/containers/detection_engine/alerts/use_signal_index'; import { SetAbsoluteRangeDatePicker } from '../../../network/pages/types'; import { Filter, IIndexPattern, Query } from '../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../../common/store'; @@ -65,10 +65,10 @@ const SignalsByCategoryComponent: React.FC = ({ ); const defaultStackByOption = - signalsHistogramOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? signalsHistogramOptions[0]; + alertsHistogramOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? alertsHistogramOptions[0]; return ( - = ({ query={query} signalIndexName={signalIndexName} setQuery={setQuery} - showTotalSignalsCount={true} - showLinkToSignals={onlyField == null ? true : false} - stackByOptions={onlyField == null ? signalsHistogramOptions : undefined} + showTotalAlertsCount={true} + showLinkToAlerts={onlyField == null ? true : false} + stackByOptions={onlyField == null ? alertsHistogramOptions : undefined} legendPosition={'right'} to={to} - title={i18n.SIGNAL_COUNT} + title={i18n.ALERT_COUNT} updateDateRange={updateDateRangeCallback} /> ); diff --git a/x-pack/plugins/siem/public/overview/pages/translations.ts b/x-pack/plugins/siem/public/overview/pages/translations.ts index b7bee15e4c5bf..7c0c00029266c 100644 --- a/x-pack/plugins/siem/public/overview/pages/translations.ts +++ b/x-pack/plugins/siem/public/overview/pages/translations.ts @@ -34,8 +34,8 @@ export const RECENT_TIMELINES = i18n.translate('xpack.siem.overview.recentTimeli defaultMessage: 'Recent timelines', }); -export const SIGNAL_COUNT = i18n.translate('xpack.siem.overview.signalCountTitle', { - defaultMessage: 'Signal count', +export const ALERT_COUNT = i18n.translate('xpack.siem.overview.alertCountTitle', { + defaultMessage: 'Alert count', }); export const TOP = (fieldName: string) => diff --git a/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx b/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx index 1136b7c8d0dc4..d018487a1ccd8 100644 --- a/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx +++ b/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx @@ -16,8 +16,8 @@ import React, { useCallback } from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../common/containers/source'; -import { signalsHeaders } from '../../../alerts/components/signals/default_config'; -import { alertsHeaders } from '../../../common/components/alerts_viewer/default_headers'; +import { alertsHeaders } from '../../../alerts/components/alerts_table/default_config'; +import { alertsHeaders as externalAlertsHeaders } from '../../../common/components/alerts_viewer/default_headers'; import { defaultHeaders as eventsDefaultHeaders } from '../../../common/components/events_viewer/default_headers'; import { defaultHeaders } from '../timeline/body/column_headers/default_headers'; import { OnUpdateColumns } from '../timeline/events'; @@ -104,10 +104,10 @@ const TitleRow = React.memo<{ const handleResetColumns = useCallback(() => { let resetDefaultHeaders = defaultHeaders; if (isEventViewer) { - if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'alerts') { + if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'externalAlerts') { + resetDefaultHeaders = externalAlertsHeaders; + } else if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'alerts') { resetDefaultHeaders = alertsHeaders; - } else if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'signals') { - resetDefaultHeaders = signalsHeaders; } else { resetDefaultHeaders = eventsDefaultHeaders; } diff --git a/x-pack/plugins/siem/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/siem/public/timelines/components/flyout/header/index.tsx index b332260597f22..ab8a24889e9bf 100644 --- a/x-pack/plugins/siem/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/siem/public/timelines/components/flyout/header/index.tsx @@ -39,6 +39,7 @@ const StatefulFlyoutHeader = React.memo( title, noteIds, notesById, + status, timelineId, toggleLock, updateDescription, @@ -62,6 +63,7 @@ const StatefulFlyoutHeader = React.memo( isFavorite={isFavorite} title={title} noteIds={noteIds} + status={status} timelineId={timelineId} toggleLock={toggleLock} updateDescription={updateDescription} @@ -94,6 +96,7 @@ const makeMapStateToProps = () => { kqlQuery, title = '', noteIds = emptyNotesId, + status, } = timeline; const history = emptyHistory; // TODO: get history from store via selector @@ -107,6 +110,7 @@ const makeMapStateToProps = () => { isFavorite, isDatepickerLocked: globalInput.linkTo.includes('timeline'), noteIds, + status, title, }; }; diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx index 3d1a9075386c1..c52be64f94bf1 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx @@ -9,7 +9,7 @@ import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { WithSource } from '../../../common/containers/source'; -import { useSignalIndex } from '../../../alerts/containers/detection_engine/signals/use_signal_index'; +import { useSignalIndex } from '../../../alerts/containers/detection_engine/alerts/use_signal_index'; import { inputsModel, inputsSelectors, State } from '../../../common/store'; import { timelineActions, timelineSelectors } from '../../store/timeline'; import { ColumnHeaderOptions, TimelineModel } from '../../../timelines/store/timeline/model'; diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/properties/helpers.tsx index 3b1d324f3444d..3ef10d394bc7b 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/properties/helpers.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/properties/helpers.tsx @@ -23,6 +23,7 @@ import styled from 'styled-components'; import { useHistory } from 'react-router-dom'; import { useSelector } from 'react-redux'; +import { TimelineStatus } from '../../../../../common/types/timeline'; import { Note } from '../../../../common/lib/note'; import { Notes } from '../../notes'; import { AssociateNote, UpdateNote } from '../../notes/helpers'; @@ -119,40 +120,44 @@ Name.displayName = 'Name'; interface NewCaseProps { onClosePopover: () => void; timelineId: string; + timelineStatus: TimelineStatus; timelineTitle: string; } -export const NewCase = React.memo(({ onClosePopover, timelineId, timelineTitle }) => { - const history = useHistory(); - const { savedObjectId } = useSelector((state: State) => - timelineSelectors.selectTimeline(state, timelineId) - ); - const handleClick = useCallback(() => { - onClosePopover(); - history.push({ - pathname: `/${SiemPageName.case}/create`, - state: { - insertTimeline: { - timelineId, - timelineSavedObjectId: savedObjectId, - timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE, +export const NewCase = React.memo( + ({ onClosePopover, timelineId, timelineStatus, timelineTitle }) => { + const history = useHistory(); + const { savedObjectId } = useSelector((state: State) => + timelineSelectors.selectTimeline(state, timelineId) + ); + const handleClick = useCallback(() => { + onClosePopover(); + history.push({ + pathname: `/${SiemPageName.case}/create`, + state: { + insertTimeline: { + timelineId, + timelineSavedObjectId: savedObjectId, + timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE, + }, }, - }, - }); - }, [onClosePopover, history, timelineId, timelineTitle]); + }); + }, [onClosePopover, history, timelineId, timelineTitle]); - return ( - - {i18n.ATTACH_TIMELINE_TO_NEW_CASE} - - ); -}); + return ( + + {i18n.ATTACH_TIMELINE_TO_NEW_CASE} + + ); + } +); NewCase.displayName = 'NewCase'; interface NewTimelineProps { diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.test.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.test.tsx index bfa32fecac89b..8bdec78ec8da2 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.test.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.test.tsx @@ -8,6 +8,7 @@ import { mount } from 'enzyme'; import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; +import { TimelineStatus } from '../../../../../common/types/timeline'; import { mockGlobalState, apolloClientObservable, @@ -25,6 +26,24 @@ jest.mock('../../../../common/components/utils'); width: mockedWidth, })); +jest.mock('react-redux', () => { + const originalModule = jest.requireActual('react-redux'); + + return { + ...originalModule, + useSelector: jest.fn().mockReturnValue({ savedObjectId: '1' }), + }; +}); + +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + + return { + ...originalModule, + useHistory: jest.fn(), + }; +}); + describe('Properties', () => { const usersViewing = ['elastic']; @@ -50,6 +69,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -60,7 +80,45 @@ describe('Properties', () => { /> ); + + wrapper.find('[data-test-subj="settings-gear"]').at(0).simulate('click'); + expect(wrapper.find('[data-test-subj="timeline-properties"]').exists()).toEqual(true); + expect(wrapper.find('button[data-test-subj="attach-timeline-case"]').prop('disabled')).toEqual( + false + ); + }); + + test('renders correctly draft timeline', () => { + const wrapper = mount( + + + + ); + + wrapper.find('[data-test-subj="settings-gear"]').at(0).simulate('click'); + + expect(wrapper.find('button[data-test-subj="attach-timeline-case"]').prop('disabled')).toEqual( + true + ); }); test('it renders an empty star icon when it is NOT a favorite', () => { @@ -76,6 +134,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -103,6 +162,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -132,6 +192,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -159,6 +220,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -191,6 +253,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -222,6 +285,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -256,6 +320,7 @@ describe('Properties', () => { description={description} getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -292,6 +357,7 @@ describe('Properties', () => { description={description} getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -326,6 +392,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -360,6 +427,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -392,6 +460,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -421,6 +490,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} @@ -448,6 +518,7 @@ describe('Properties', () => { description="" getNotesByIds={jest.fn()} noteIds={[]} + status={TimelineStatus.active} timelineId="abc" toggleLock={jest.fn()} updateDescription={jest.fn()} diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.tsx index 502cc85ce907a..d8966a58748ed 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/properties/index.tsx @@ -6,6 +6,7 @@ import React, { useState, useCallback, useMemo } from 'react'; +import { TimelineStatus } from '../../../../../common/types/timeline'; import { useThrottledResizeObserver } from '../../../../common/components/utils'; import { Note } from '../../../../common/lib/note'; import { InputsModelId } from '../../../../common/store/inputs/constants'; @@ -31,6 +32,7 @@ interface Props { isFavorite: boolean; noteIds: string[]; timelineId: string; + status: TimelineStatus; title: string; toggleLock: ToggleLock; updateDescription: UpdateDescription; @@ -62,6 +64,7 @@ export const Properties = React.memo( isDatepickerLocked, isFavorite, noteIds, + status, timelineId, title, toggleLock, @@ -140,6 +143,7 @@ export const Properties = React.memo( showNotesFromWidth={width < showNotesThreshold} showTimelineModal={showTimelineModal} showUsersView={title.length > 0} + status={status} timelineId={timelineId} title={title} updateDescription={updateDescription} diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/properties/properties_right.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/properties/properties_right.tsx index 4149a958e889d..963b67838e811 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/properties/properties_right.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/properties/properties_right.tsx @@ -22,6 +22,7 @@ import { InspectButton, InspectButtonContainer } from '../../../../common/compon import * as i18n from './translations'; import { AssociateNote } from '../../notes/helpers'; import { Note } from '../../../../common/lib/note'; +import { TimelineStatus } from '../../../../../common/types/timeline'; export const PropertiesRightStyle = styled(EuiFlexGroup)` margin-right: 5px; @@ -79,6 +80,7 @@ interface Props { onCloseTimelineModal: () => void; onOpenTimelineModal: () => void; showTimelineModal: boolean; + status: TimelineStatus; title: string; updateNote: UpdateNote; } @@ -106,6 +108,7 @@ const PropertiesRightComponent: React.FC = ({ onCloseTimelineModal, onOpenTimelineModal, title, + status, }) => ( @@ -142,6 +145,7 @@ const PropertiesRightComponent: React.FC = ({ onClosePopover={onClosePopover} timelineId={timelineId} timelineTitle={title} + timelineStatus={status} /> diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx index 85097f93464b3..5a3805af0ca43 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx @@ -60,7 +60,7 @@ export const eventTypeOptions: EventTypeOptionItem[] = [ inputDisplay: {i18n.RAW_EVENT}, }, { - value: 'signal', + value: 'alert', inputDisplay: {i18n.SIGNAL_EVENT}, }, ]; diff --git a/x-pack/plugins/siem/public/timelines/containers/index.tsx b/x-pack/plugins/siem/public/timelines/containers/index.tsx index 76f6bdd36ecec..5efcb84539123 100644 --- a/x-pack/plugins/siem/public/timelines/containers/index.tsx +++ b/x-pack/plugins/siem/public/timelines/containers/index.tsx @@ -27,7 +27,7 @@ import { QueryTemplate, QueryTemplateProps } from '../../common/containers/query import { EventType } from '../../timelines/store/timeline/model'; import { timelineQuery } from './index.gql_query'; import { timelineActions } from '../../timelines/store/timeline'; -import { SIGNALS_PAGE_TIMELINE_ID } from '../../alerts/components/signals'; +import { ALERTS_TABLE_TIMELINE_ID } from '../../alerts/components/alerts_table'; export interface TimelineArgs { events: TimelineItem[]; @@ -182,7 +182,7 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch: Dispatch) => ({ clearSignalsState: ({ id }: { id?: string }) => { - if (id != null && id === SIGNALS_PAGE_TIMELINE_ID) { + if (id != null && id === ALERTS_TABLE_TIMELINE_ID) { dispatch(timelineActions.clearEventsLoading({ id })); dispatch(timelineActions.clearEventsDeleted({ id })); } diff --git a/x-pack/plugins/siem/public/timelines/containers/one/index.gql_query.ts b/x-pack/plugins/siem/public/timelines/containers/one/index.gql_query.ts index d70a419b99a3b..47e80b005fb99 100644 --- a/x-pack/plugins/siem/public/timelines/containers/one/index.gql_query.ts +++ b/x-pack/plugins/siem/public/timelines/containers/one/index.gql_query.ts @@ -128,6 +128,7 @@ export const oneTimelineQuery = gql` updatedBy version } + status title timelineType templateTimelineId diff --git a/x-pack/plugins/siem/public/timelines/store/timeline/model.ts b/x-pack/plugins/siem/public/timelines/store/timeline/model.ts index df5e2a99e0248..f7e848e8a9e1b 100644 --- a/x-pack/plugins/siem/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/siem/public/timelines/store/timeline/model.ts @@ -14,11 +14,11 @@ import { TimelineType, TimelineStatus, } from '../../../graphql/types'; -import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/model'; +import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/types'; export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages export type KqlMode = 'filter' | 'search'; -export type EventType = 'all' | 'raw' | 'signal'; +export type EventType = 'all' | 'raw' | 'alert'; export type ColumnHeaderType = 'not-filtered' | 'text-filter'; diff --git a/x-pack/plugins/siem/public/timelines/store/timeline/selectors.ts b/x-pack/plugins/siem/public/timelines/store/timeline/selectors.ts index d4c8f3cf3a2c2..af7ac075468c3 100644 --- a/x-pack/plugins/siem/public/timelines/store/timeline/selectors.ts +++ b/x-pack/plugins/siem/public/timelines/store/timeline/selectors.ts @@ -7,7 +7,7 @@ import { createSelector } from 'reselect'; import { isFromKueryExpressionValid } from '../../../common/lib/keury'; -import { State } from '../../../common/store/reducer'; +import { State } from '../../../common/store/types'; import { TimelineModel } from './model'; import { AutoSavedWarningMsg, TimelineById } from './types'; diff --git a/x-pack/plugins/siem/scripts/endpoint/resolver_generator.ts b/x-pack/plugins/siem/scripts/endpoint/resolver_generator.ts index 77bf200eeb540..26c6e5ccc28a8 100644 --- a/x-pack/plugins/siem/scripts/endpoint/resolver_generator.ts +++ b/x-pack/plugins/siem/scripts/endpoint/resolver_generator.ts @@ -70,7 +70,7 @@ async function main() { metadataIndex: { alias: 'mi', describe: 'index to store host metadata in', - default: 'metrics-endpoint-default-1', + default: 'metrics-endpoint.metadata-default-1', type: 'string', }, policyIndex: { diff --git a/x-pack/plugins/siem/server/endpoint/alerts/index_pattern.ts b/x-pack/plugins/siem/server/endpoint/alerts/index_pattern.ts index 1cbdf96c5bcee..391aedecdd099 100644 --- a/x-pack/plugins/siem/server/endpoint/alerts/index_pattern.ts +++ b/x-pack/plugins/siem/server/endpoint/alerts/index_pattern.ts @@ -22,6 +22,7 @@ export interface IndexPatternRetriever { export class IngestIndexPatternRetriever implements IndexPatternRetriever { private static endpointPackageName = 'endpoint'; private static metadataDataset = 'metadata'; + private static policyDataset = 'policy'; private readonly log: Logger; constructor(private readonly service: ESIndexPatternService, loggerFactory: LoggerFactory) { this.log = loggerFactory.get('index-pattern-retriever'); @@ -76,7 +77,7 @@ export class IngestIndexPatternRetriever implements IndexPatternRetriever { } } - getPolicyResponseIndexPattern(ctx: RequestHandlerContext): Promise { - return Promise.resolve('metrics-endpoint.policy-default-1'); + async getPolicyResponseIndexPattern(ctx: RequestHandlerContext): Promise { + return this.getIndexPattern(ctx, IngestIndexPatternRetriever.policyDataset); } } diff --git a/x-pack/plugins/siem/server/lib/detection_engine/README.md b/x-pack/plugins/siem/server/lib/detection_engine/README.md index 695165e1990a9..4c90869a9fe84 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/README.md +++ b/x-pack/plugins/siem/server/lib/detection_engine/README.md @@ -152,7 +152,7 @@ logging.events: ``` See these two README.md's pages for more references on the alerting and actions API: -https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerting/README.md +https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerts/README.md https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions ### Signals API diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts index e0414f842ceb3..440efc8d0d5a3 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { createNotifications } from './create_notifications'; describe('createNotifications', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts index 35a737177ad49..a472d8a4df4a4 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Alert } from '../../../../../alerting/common'; +import { Alert } from '../../../../../alerts/common'; import { APP_ID, NOTIFICATIONS_ID } from '../../../../common/constants'; import { CreateNotificationParams } from './types'; import { addTags } from './add_tags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/delete_notifications.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/delete_notifications.test.ts index 089822f486aeb..2f754c126771a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/delete_notifications.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/delete_notifications.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { deleteNotifications } from './delete_notifications'; import { readNotifications } from './read_notifications'; jest.mock('./read_notifications'); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/find_notifications.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/find_notifications.ts index b47ea348bd4d6..5d3a328dd6fbb 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/find_notifications.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/find_notifications.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FindResult } from '../../../../../alerting/server'; +import { FindResult } from '../../../../../alerts/server'; import { NOTIFICATIONS_ID } from '../../../../common/constants'; import { FindNotificationParams } from './types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts index 69f37da1e225b..038a8916c87d5 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { buildSignalsSearchQuery } from './build_signals_query'; interface GetSignalsCount { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.test.ts index 961aac15c484d..a46f65da58043 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.test.ts @@ -5,7 +5,7 @@ */ import { readNotifications } from './read_notifications'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getNotificationResult, getFindNotificationsResultWithSingleHit, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.ts index c585c474556a1..fe9101335b4f5 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/read_notifications.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SanitizedAlert } from '../../../../../alerting/common'; +import { SanitizedAlert } from '../../../../../alerts/common'; import { ReadNotificationParams, isAlertType } from './types'; import { findNotifications } from './find_notifications'; import { INTERNAL_RULE_ALERT_ID_KEY } from '../../../../common/constants'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts index e8d778bddadc2..47356679c8075 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.test.ts @@ -8,7 +8,7 @@ import { loggingServiceMock } from 'src/core/server/mocks'; import { getResult } from '../routes/__mocks__/request_responses'; import { rulesNotificationAlertType } from './rules_notification_alert_type'; import { buildSignalsSearchQuery } from './build_signals_query'; -import { alertsMock, AlertServicesMock } from '../../../../../../plugins/alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; import { NotificationExecutorOptions } from './types'; jest.mock('./build_signals_query'); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts index a0bd5e092c6ea..a26ddfc90434a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts @@ -5,7 +5,7 @@ */ import { mapKeys, snakeCase } from 'lodash/fp'; -import { AlertInstance } from '../../../../../alerting/server'; +import { AlertInstance } from '../../../../../alerts/server'; import { RuleTypeParams } from '../types'; export type NotificationRuleTypeParams = RuleTypeParams & { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/types.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/types.ts index d0bb1bfdfb1c4..1345bf2ac6339 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/types.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/types.ts @@ -10,8 +10,8 @@ import { AlertType, State, AlertExecutorOptions, -} from '../../../../../alerting/server'; -import { Alert } from '../../../../../alerting/common'; +} from '../../../../../alerts/server'; +import { Alert } from '../../../../../alerts/common'; import { NOTIFICATIONS_ID } from '../../../../common/constants'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts index b9dc42b96696d..c7763c7ed7e77 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { updateNotifications } from './update_notifications'; import { readNotifications } from './read_notifications'; import { createNotifications } from './create_notifications'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts b/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts index 5889b0e4dcfb8..17024c7c0d75f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PartialAlert } from '../../../../../alerting/server'; +import { PartialAlert } from '../../../../../alerts/server'; import { readNotifications } from './read_notifications'; import { UpdateNotificationParams } from './types'; import { addTags } from './add_tags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts index a24375c368d63..65f38507605a5 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -10,7 +10,7 @@ import { elasticsearchServiceMock, savedObjectsClientMock, } from '../../../../../../../../src/core/server/mocks'; -import { alertsClientMock } from '../../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../../alerts/server/mocks'; import { licensingMock } from '../../../../../../licensing/server/mocks'; import { siemMock } from '../../../../mocks'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index cce23d3a0d59a..9246d8cb3519b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -641,7 +641,7 @@ export const getEmptySignalsResponse = (): SignalSearchResponse => ({ _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, hits: { total: { value: 0, relation: 'eq' }, max_score: 0, hits: [] }, aggregations: { - signalsByGrouping: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, + alertsByGrouping: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, }, }); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 83dd87002e8f9..6268451042da1 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + PrePackagedRulesSchema, + prePackagedRulesSchema, +} from '../../../../../common/detection_engine/schemas/response/prepackaged_rules_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constants'; import { getIndexExists } from '../../index/get_index_exists'; @@ -14,10 +18,6 @@ import { updatePrepackagedRules } from '../../rules/update_prepacked_rules'; import { getRulesToInstall } from '../../rules/get_rules_to_install'; import { getRulesToUpdate } from '../../rules/get_rules_to_update'; import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; -import { - PrePackagedRulesSchema, - prePackagedRulesSchema, -} from '../schemas/response/prepackaged_rules_schema'; import { validate } from './validate'; export const addPrepackedRulesRoute = (router: IRouter) => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index ff6d212deb584..d88cc7fcde504 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -6,6 +6,7 @@ import uuid from 'uuid'; +import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; @@ -24,7 +25,6 @@ import { buildSiemResponse, } from '../utils'; import { createRulesBulkSchema } from '../schemas/create_rules_bulk_schema'; -import { rulesBulkSchema } from '../schemas/response/rules_bulk_schema'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts index 036e29aa0ebe7..01ad3c7d4e726 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import { IRouter, RouteConfig, RequestHandler } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { queryRulesBulkSchema } from '../schemas/query_rules_bulk_schema'; -import { rulesBulkSchema } from '../schemas/response/rules_bulk_schema'; import { getIdBulkError } from './utils'; import { transformValidateBulkError, validate } from './validate'; import { transformBulkError, buildRouteValidation, buildSiemResponse } from '../utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts index 67a54f3ba492a..90380b0483c82 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + PrePackagedRulesStatusSchema, + prePackagedRulesStatusSchema, +} from '../../../../../common/detection_engine/schemas/response/prepackaged_rules_status_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constants'; import { transformError, buildSiemResponse } from '../utils'; @@ -12,10 +16,6 @@ import { getRulesToInstall } from '../../rules/get_rules_to_install'; import { getRulesToUpdate } from '../../rules/get_rules_to_update'; import { findRules } from '../../rules/find_rules'; import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; -import { - PrePackagedRulesStatusSchema, - prePackagedRulesStatusSchema, -} from '../schemas/response/prepackaged_rules_status_schema'; import { validate } from './validate'; export const getPrepackagedRulesStatusRoute = (router: IRouter) => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts index 901e7403bb953..311149087cc49 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -7,6 +7,10 @@ import { chunk } from 'lodash/fp'; import { extname } from 'path'; +import { + ImportRulesSchema, + importRulesSchema, +} from '../../../../../common/detection_engine/schemas/response/import_rules_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { createPromiseFromStreams } from '../../../../../../../../src/legacy/utils/streams'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; @@ -31,7 +35,6 @@ import { import { ImportRuleAlertRest } from '../../types'; import { patchRules } from '../../rules/patch_rules'; import { importRulesQuerySchema, importRulesPayloadSchema } from '../schemas/import_rules_schema'; -import { ImportRulesSchema, importRulesSchema } from '../schemas/response/import_rules_schema'; import { getTupleDuplicateErrorsAndUniqueRules } from './utils'; import { validate } from './validate'; import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index cc4d5e03500a4..e9c0ca08c88ee 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; @@ -14,7 +15,6 @@ import { transformBulkError, buildRouteValidation, buildSiemResponse } from '../ import { getIdBulkError } from './utils'; import { transformValidateBulkError, validate } from './validate'; import { patchRulesBulkSchema } from '../schemas/patch_rules_bulk_schema'; -import { rulesBulkSchema } from '../schemas/response/rules_bulk_schema'; import { patchRules } from '../../rules/patch_rules'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; @@ -122,6 +122,7 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => version, anomalyThreshold, machineLearningJobId, + actions, }); if (rule != null && rule.enabled != null && rule.name != null) { const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts index ae23e0efc857d..2a1ac9862e7d0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -118,6 +118,7 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { version, anomalyThreshold, machineLearningJobId, + actions, }); if (rule != null && rule.enabled != null && rule.name != null) { const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index c0dfecc71ce05..335684dc38b32 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import { IRouter } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; @@ -15,7 +16,6 @@ import { transformValidateBulkError, validate } from './validate'; import { buildRouteValidation, transformBulkError, buildSiemResponse } from '../utils'; import { updateRulesBulkSchema } from '../schemas/update_rules_bulk_schema'; import { updateRules } from '../../rules/update_rules'; -import { rulesBulkSchema } from '../schemas/response/rules_bulk_schema'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts index ec9e84d4fa6bb..df158d23c0e24 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts @@ -23,8 +23,8 @@ import { ImportRuleAlertRest, RuleAlertParamsRest, RuleTypeParams } from '../../ import { BulkError, ImportSuccessError } from '../utils'; import { getSimpleRule, getOutputRuleAlertForRest } from '../__mocks__/utils'; import { createPromiseFromStreams } from '../../../../../../../../src/legacy/utils/streams'; -import { PartialAlert } from '../../../../../../alerting/server'; -import { SanitizedAlert } from '../../../../../../alerting/server/types'; +import { PartialAlert } from '../../../../../../alerts/server'; +import { SanitizedAlert } from '../../../../../../alerts/server/types'; import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { RuleAlertType } from '../../rules/types'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts index 861e6463533fb..5329ff04435ca 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -8,7 +8,7 @@ import { pickBy, countBy } from 'lodash/fp'; import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; import uuid from 'uuid'; -import { PartialAlert, FindResult } from '../../../../../../alerting/server'; +import { PartialAlert, FindResult } from '../../../../../../alerts/server'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { RuleAlertType, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts index 9069202d4d3aa..07b891e2bf021 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts @@ -13,10 +13,10 @@ import { transformValidateBulkError, } from './validate'; import { getResult } from '../__mocks__/request_responses'; -import { FindResult } from '../../../../../../alerting/server'; -import { RulesSchema } from '../schemas/response/rules_schema'; +import { FindResult } from '../../../../../../alerts/server'; import { BulkError } from '../utils'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; +import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; export const ruleOutput: RulesSchema = { actions: [], diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts index cda3a4b81ed9b..5fc239ed48263 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts @@ -9,9 +9,14 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import * as t from 'io-ts'; +import { findRulesSchema } from '../../../../../common/detection_engine/schemas/response/find_rules_schema'; +import { + RulesSchema, + rulesSchema, +} from '../../../../../common/detection_engine/schemas/response/rules_schema'; import { formatErrors } from '../../../../../common/format_errors'; import { exactCheck } from '../../../../../common/exact_check'; -import { PartialAlert, FindResult } from '../../../../../../alerting/server'; +import { PartialAlert, FindResult } from '../../../../../../alerts/server'; import { isAlertType, IRuleSavedAttributesSavedObjectAttributes, @@ -19,9 +24,7 @@ import { } from '../../rules/types'; import { OutputRuleAlertRest } from '../../types'; import { createBulkErrorObject, BulkError } from '../utils'; -import { rulesSchema, RulesSchema } from '../schemas/response/rules_schema'; import { transformFindAlerts, transform, transformAlertToRule } from './utils'; -import { findRulesSchema } from '../schemas/response/find_rules_schema'; import { RuleActions } from '../../rule_actions/types'; export const transformValidateFindAlerts = ( diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts index 226dea7c20344..66356a1d65352 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { ThreatParams, PrepackagedRules } from '../../types'; import { addPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts index 1e2941015b735..013db2020a146 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { createRulesSchema } from './create_rules_schema'; import { PatchRuleAlertParamsRest } from '../../rules/types'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts index d28530ffb789e..cb03c4781cb6c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { importRulesSchema, importRulesQuerySchema, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts index 755c0b2ccaa3f..218cae68db036 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { patchRulesSchema } from './patch_rules_schema'; import { PatchRuleAlertParamsRest } from '../../rules/types'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.ts deleted file mode 100644 index 1c1bee58f0c97..0000000000000 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as t from 'io-ts'; -import { Either, left, fold } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import { isMlRule } from '../../../../../../common/machine_learning/helpers'; -import { - dependentRulesSchema, - RequiredRulesSchema, - partialRulesSchema, - requiredRulesSchema, -} from './rules_schema'; -import { typeAndTimelineOnlySchema, TypeAndTimelineOnly } from './type_timeline_only_schema'; - -export const addSavedId = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.type === 'saved_query') { - return [t.exact(t.type({ saved_id: dependentRulesSchema.props.saved_id }))]; - } else { - return []; - } -}; - -export const addTimelineTitle = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.timeline_id != null) { - return [ - t.exact(t.type({ timeline_title: dependentRulesSchema.props.timeline_title })), - t.exact(t.type({ timeline_id: dependentRulesSchema.props.timeline_id })), - ]; - } else { - return []; - } -}; - -export const addQueryFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.type === 'query' || typeAndTimelineOnly.type === 'saved_query') { - return [ - t.exact(t.type({ query: dependentRulesSchema.props.query })), - t.exact(t.type({ language: dependentRulesSchema.props.language })), - ]; - } else { - return []; - } -}; - -export const addMlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (isMlRule(typeAndTimelineOnly.type)) { - return [ - t.exact(t.type({ anomaly_threshold: dependentRulesSchema.props.anomaly_threshold })), - t.exact( - t.type({ machine_learning_job_id: dependentRulesSchema.props.machine_learning_job_id }) - ), - ]; - } else { - return []; - } -}; - -export const getDependents = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed => { - const dependents: t.Mixed[] = [ - t.exact(requiredRulesSchema), - t.exact(partialRulesSchema), - ...addSavedId(typeAndTimelineOnly), - ...addTimelineTitle(typeAndTimelineOnly), - ...addQueryFields(typeAndTimelineOnly), - ...addMlFields(typeAndTimelineOnly), - ]; - - if (dependents.length > 1) { - // This unsafe cast is because t.intersection does not use an array but rather a set of - // tuples and really does not look like they expected us to ever dynamically build up - // intersections, but here we are doing that. Looking at their code, although they limit - // the array elements to 5, it looks like you have N number of intersections - const unsafeCast: [t.Mixed, t.Mixed] = dependents as [t.Mixed, t.Mixed]; - return t.intersection(unsafeCast); - } else { - // We are not allowed to call t.intersection with a single value so we return without - // it here normally. - return dependents[0]; - } -}; - -export const checkTypeDependents = (input: unknown): Either => { - const typeOnlyDecoded = typeAndTimelineOnlySchema.decode(input); - const onLeft = (errors: t.Errors): Either => left(errors); - const onRight = ( - typeAndTimelineOnly: TypeAndTimelineOnly - ): Either => { - const intersections = getDependents(typeAndTimelineOnly); - return intersections.decode(input); - }; - return pipe(typeOnlyDecoded, fold(onLeft, onRight)); -}; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts deleted file mode 100644 index ade4d12517aca..0000000000000 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { left } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import { rulesSchema, RulesSchema, removeList } from './rules_schema'; -import { getBaseResponsePayload } from './__mocks__/utils'; -import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; -import { exactCheck } from '../../../../../../common/exact_check'; - -export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; - -describe('rules_schema', () => { - beforeAll(() => { - setFeatureFlagsForTestsOnly(); - }); - - afterAll(() => { - unSetFeatureFlagsForTestsOnly(); - }); - - test('it should validate a type of "query" without anything extra', () => { - const payload = getBaseResponsePayload(); - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); - payload.invalid_extra_data = 'invalid_extra_data'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate invalid_data for the type', () => { - const payload: Omit & { type: string } = getBaseResponsePayload(); - payload.type = 'invalid_data'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "invalid_data" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getBaseResponsePayload(); - payload.type = 'query'; - payload.saved_id = 'save id 123'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getBaseResponsePayload(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); - - expected.type = 'saved_query'; - expected.saved_id = 'save id 123'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getBaseResponsePayload(); - payload.type = 'saved_query'; - delete payload.saved_id; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "saved_id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { - const payload = getBaseResponsePayload(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); - expected.timeline_id = 'some timeline id'; - expected.timeline_title = 'some timeline title'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getBaseResponsePayload(); - payload.timeline_id = 'some timeline id'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getBaseResponsePayload(); - payload.timeline_title = 'some timeline title'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { - const payload = getBaseResponsePayload(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_title = 'some timeline title'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { - const payload = getBaseResponsePayload(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_id = 'some timeline id'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - // TODO: (LIST-FEATURE) Remove this test once the feature flag is deployed - test('it should remove exceptions_list when we need it to be removed because the feature is off but there exists a list in the data', () => { - const payload = getBaseResponsePayload(); - const decoded = rulesSchema.decode(payload); - const listRemoved = removeList(decoded); - const message = pipe(listRemoved, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', - created_at: '2020-02-20T03:57:54.037Z', - updated_at: '2020-02-20T03:57:54.037Z', - created_by: 'elastic', - description: 'some description', - enabled: true, - false_positives: ['false positive 1', 'false positive 2'], - from: 'now-6m', - immutable: false, - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - references: ['test 1', 'test 2'], - severity: 'high', - updated_by: 'elastic_kibana', - tags: [], - to: 'now', - type: 'query', - threat: [], - version: 1, - output_index: '.siem-signals-hassanabad-frank-default', - max_signals: 100, - risk_score: 55, - language: 'kuery', - rule_id: 'query-rule-id', - interval: '5m', - status: 'succeeded', - status_date: '2020-02-22T16:47:50.047Z', - last_success_at: '2020-02-22T16:47:50.047Z', - last_success_message: 'succeeded', - }); - }); - - test('it should work with exceptions_list that are not there and not cause invalidation or errors', () => { - const payload = getBaseResponsePayload(); - const { exceptions_list, ...payloadWithoutLists } = payload; - const decoded = rulesSchema.decode(payloadWithoutLists); - const listRemoved = removeList(decoded); - const message = pipe(listRemoved, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', - created_at: '2020-02-20T03:57:54.037Z', - updated_at: '2020-02-20T03:57:54.037Z', - created_by: 'elastic', - description: 'some description', - enabled: true, - false_positives: ['false positive 1', 'false positive 2'], - from: 'now-6m', - immutable: false, - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - references: ['test 1', 'test 2'], - severity: 'high', - updated_by: 'elastic_kibana', - tags: [], - to: 'now', - type: 'query', - threat: [], - version: 1, - output_index: '.siem-signals-hassanabad-frank-default', - max_signals: 100, - risk_score: 55, - language: 'kuery', - rule_id: 'query-rule-id', - interval: '5m', - status: 'succeeded', - status_date: '2020-02-22T16:47:50.047Z', - last_success_at: '2020-02-22T16:47:50.047Z', - last_success_message: 'succeeded', - }); - }); -}); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts index b89df0fc0f3ab..8bda16de97775 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertAction } from '../../../../../../alerting/common'; +import { AlertAction } from '../../../../../../alerts/common'; import { updateRulesSchema } from './update_rules_schema'; import { PatchRuleAlertParamsRest } from '../../rules/types'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts index 26c3b29ff2c51..2ff6d6ac646ae 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts @@ -5,7 +5,7 @@ */ import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { IRuleActionsAttributesSavedObjectAttributes } from './types'; import { getThrottleOptions, getRuleActionsFromSavedObject } from './utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts index 251f9155f9331..3d5734b13ea48 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { getRuleActionsSavedObject } from './get_rule_actions_saved_object'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts index 83cd59f0a1cde..c36f6ca831c57 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts @@ -5,7 +5,7 @@ */ import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { IRuleActionsAttributesSavedObjectAttributes } from './types'; import { getRuleActionsFromSavedObject } from './utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts index 3364827d397d2..c650de2a5e2b9 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { getRuleActionsSavedObject } from './get_rule_actions_saved_object'; import { createRuleActionsSavedObject } from './create_rule_actions_saved_object'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts index c8a3b1bbc38ad..fd3d107103f19 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { RulesActionsSavedObject } from './get_rule_actions_saved_object'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts index f4f0a8042d0a5..f086166d0685e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getMlResult } from '../routes/__mocks__/request_responses'; import { createRules } from './create_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.ts index a007fe35b407e..67e066c6670fb 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/create_rules.ts @@ -5,7 +5,7 @@ */ import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { Alert } from '../../../../../alerting/common'; +import { Alert } from '../../../../../alerts/common'; import { APP_ID, SIGNALS_ID } from '../../../../common/constants'; import { CreateRuleParams } from './types'; import { addTags } from './add_tags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/delete_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/delete_rules.test.ts index 6bc5fc2a88b6d..f96a9e38d6a6c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/delete_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/delete_rules.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { deleteRules } from './delete_rules'; import { readRules } from './read_rules'; jest.mock('./read_rules'); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/find_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/find_rules.ts index ac600b0b5b218..c634f07387825 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/find_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/find_rules.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FindResult } from '../../../../../alerting/server'; +import { FindResult } from '../../../../../alerts/server'; import { SIGNALS_ID } from '../../../../common/constants'; import { FindRuleParams } from './types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts index d79b428a2f76d..203a23402f097 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getResult, getFindResultWithSingleHit, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts index 512164fc3d2e1..a3119131a0037 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts @@ -5,7 +5,7 @@ */ import { INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants'; -import { AlertsClient } from '../../../../../alerting/server'; +import { AlertsClient } from '../../../../../alerts/server'; import { RuleAlertType, isAlertTypes } from './types'; import { findRules } from './find_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts index 6df250f1cf513..ee21c33540024 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts @@ -9,7 +9,7 @@ import { getFindResultWithSingleHit, FindHit, } from '../routes/__mocks__/request_responses'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getExportAll } from './get_export_all'; import { unSetFeatureFlagsForTestsOnly, setFeatureFlagsForTestsOnly } from '../feature_flags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts index 06e70f0bad184..433da2be6b347 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertsClient } from '../../../../../alerting/server'; +import { AlertsClient } from '../../../../../alerts/server'; import { getNonPackagedRules } from './get_existing_prepackaged_rules'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { transformAlertsToRules } from '../routes/rules/utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts index 092a9a8faf395..b00b7353a370f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -11,7 +11,7 @@ import { FindHit, } from '../routes/__mocks__/request_responses'; import * as readRules from './read_rules'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../feature_flags'; describe('get_export_by_object_ids', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts index beaaaa8701c87..38cf8008f65c8 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertsClient } from '../../../../../alerting/server'; +import { AlertsClient } from '../../../../../alerts/server'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { isAlertType } from '../rules/types'; import { readRules } from './read_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts index 0266d702b3dcc..7b2cef9060f8c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Alert } from '../../../../../alerting/common'; -import { AlertsClient } from '../../../../../alerting/server'; +import { Alert } from '../../../../../alerts/common'; +import { AlertsClient } from '../../../../../alerts/server'; import { createRules } from './create_rules'; import { PrepackagedRules } from '../types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts index a42500223012e..0dffa665f780b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts @@ -5,7 +5,7 @@ */ import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; import { patchRules } from './patch_rules'; @@ -91,4 +91,82 @@ describe('patchRules', () => { }) ); }); + + describe('regression tests', () => { + it("updates the rule's actions if provided", async () => { + const existingRule = getResult(); + + const action = { + action_type_id: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }; + + await patchRules({ + alertsClient, + savedObjectsClient, + actions: [action], + rule: existingRule, + }); + + expect(alertsClient.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + actions: [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }, + ], + }), + }) + ); + }); + + it('does not update actions if none are specified', async () => { + const existingRule = { + ...getResult(), + actions: [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }, + ], + }; + + await patchRules({ + alertsClient, + savedObjectsClient, + rule: existingRule, + }); + + expect(alertsClient.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + actions: [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }, + ], + }), + }) + ); + }); + }); }); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts index 6dfb72532afbb..950a3e90fb70c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts @@ -5,7 +5,8 @@ */ import { defaults } from 'lodash/fp'; -import { PartialAlert } from '../../../../../alerting/server'; +import { PartialAlert } from '../../../../../alerts/server'; +import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { PatchRuleParams } from './types'; import { addTags } from './add_tags'; import { calculateVersion, calculateName, calculateInterval } from './utils'; @@ -44,6 +45,7 @@ export const patchRules = async ({ exceptions_list, anomalyThreshold, machineLearningJobId, + actions, }: PatchRuleParams): Promise => { if (rule == null) { return null; @@ -121,7 +123,7 @@ export const patchRules = async ({ schedule: { interval: calculateInterval(interval, rule.schedule.interval), }, - actions: rule.actions, + actions: actions?.map(transformRuleToAlertAction) ?? rule.actions, params: nextParams, }, }); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts index 600848948be0c..ef8e70c78422c 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts @@ -5,7 +5,7 @@ */ import { readRules } from './read_rules'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getResult, getFindResultWithSingleHit } from '../routes/__mocks__/request_responses'; export class TestError extends Error { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.ts index 9e0d5b3d05b3f..a8b76aeb8c453 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/read_rules.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SanitizedAlert } from '../../../../../alerting/common'; +import { SanitizedAlert } from '../../../../../alerts/common'; import { INTERNAL_RULE_ID_KEY } from '../../../../common/constants'; import { findRules } from './find_rules'; import { ReadRuleParams, isAlertType } from './types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/types.ts index d65261549232b..70d53090f81cc 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -13,8 +13,8 @@ import { SavedObjectsFindResponse, SavedObjectsClientContract, } from 'kibana/server'; -import { AlertsClient, PartialAlert } from '../../../../../alerting/server'; -import { Alert, SanitizedAlert } from '../../../../../alerting/common'; +import { AlertsClient, PartialAlert } from '../../../../../alerts/server'; +import { Alert, SanitizedAlert } from '../../../../../alerts/common'; import { SIGNALS_ID } from '../../../../common/constants'; import { RuleAlertParams, RuleTypeParams, RuleAlertParamsRest } from '../types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.test.ts index 2d77e9a707f74..ede5c51d1e5e7 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.test.ts @@ -5,7 +5,7 @@ */ import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { mockPrepackagedRule, getFindResultWithSingleHit, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts index 5063ddd5e52e2..c793d7eb9b6dc 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -5,7 +5,7 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AlertsClient } from '../../../../../alerting/server'; +import { AlertsClient } from '../../../../../alerts/server'; import { patchRules } from './patch_rules'; import { PrepackagedRules } from '../types'; import { readRules } from './read_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts index 13c601b40e4f1..222411deb37ab 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts @@ -5,7 +5,7 @@ */ import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks'; -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; import { updateRules } from './update_rules'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.ts index 711f019458096..54031b6e35bf1 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules.ts @@ -5,7 +5,7 @@ */ import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { PartialAlert } from '../../../../../alerting/server'; +import { PartialAlert } from '../../../../../alerts/server'; import { readRules } from './read_rules'; import { UpdateRuleParams } from './types'; import { addTags } from './add_tags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts index c5cf85d7ba014..8fceb8ef720b5 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts @@ -5,7 +5,7 @@ */ import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { AlertsClient, AlertServices } from '../../../../../alerting/server'; +import { AlertsClient, AlertServices } from '../../../../../alerts/server'; import { updateOrCreateRuleActionsSavedObject } from '../rule_actions/update_or_create_rule_actions_saved_object'; import { updateNotifications } from '../notifications/update_notifications'; import { RuleActions } from '../rule_actions/types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_instances.sh b/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_instances.sh index b5f272d0a8a09..a052123f0cc34 100755 --- a/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_instances.sh +++ b/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_instances.sh @@ -10,8 +10,8 @@ set -e ./check_env_variables.sh # Example: ./get_alert_instances.sh -# https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerting/README.md#get-apialert_find-find-alerts +# https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerts/README.md#get-apialert_find-find-alerts curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - -X GET ${KIBANA_URL}${SPACE_URL}/api/alert/_find \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/alerts/_find \ | jq . diff --git a/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_types.sh b/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_types.sh index 28c250e9368a6..edade604d74ce 100755 --- a/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_types.sh +++ b/x-pack/plugins/siem/server/lib/detection_engine/scripts/get_alert_types.sh @@ -10,8 +10,8 @@ set -e ./check_env_variables.sh # Example: ./get_alert_types.sh -# https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerting/README.md#get-apialerttypes-list-alert-types +# https://github.com/elastic/kibana/blob/master/x-pack/plugins/alerts/README.md#get-apialerttypes-list-alert-types curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - -X GET ${KIBANA_URL}${SPACE_URL}/api/alert/types \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/alerts/list_alert_types \ | jq . diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.test.ts index ec8db77dac725..772ebd932698b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.test.ts @@ -15,7 +15,7 @@ import { formatQuery, getLanguageBooleanOperator, } from './build_exceptions_query'; -import { List } from '../routes/schemas/types/lists_default_array'; +import { List } from '../../../../common/detection_engine/schemas/types/lists_default_array'; describe('build_exceptions_query', () => { describe('getLanguageBooleanOperator', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.ts index e7be5025a51f0..b33a2376589ef 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/build_exceptions_query.ts @@ -3,8 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { + ListOperator, + ListValues, + List, +} from '../../../../common/detection_engine/schemas/types/lists_default_array'; import { Query } from '../../../../../../../src/plugins/data/server'; -import { List, ListOperator, ListValues } from '../routes/schemas/types/lists_default_array'; import { RuleAlertParams, Language } from '../types'; type Operators = 'and' | 'or' | 'not'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts index 5862e6c481431..80839545951d5 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts @@ -9,7 +9,7 @@ import set from 'set-value'; import { SearchResponse } from 'elasticsearch'; import { Logger } from '../../../../../../../src/core/server'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes } from '../types'; import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/filter_events_with_list.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/filter_events_with_list.ts index 400bb5dda46e7..07435fda0da2e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/filter_events_with_list.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/filter_events_with_list.ts @@ -6,11 +6,11 @@ import { get } from 'lodash/fp'; import { Logger } from 'src/core/server'; +import { List } from '../../../../common/detection_engine/schemas/types/lists_default_array'; import { type } from '../../../../../lists/common/schemas/common'; import { ListClient } from '../../../../../lists/server'; import { SignalSearchResponse, SearchTypes } from './types'; import { RuleAlertParams } from '../types'; -import { List } from '../routes/schemas/types/lists_default_array'; interface FilterEventsAgainstList { listClient: ListClient; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts index 35ec1950cedaa..0930fbdb534f5 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts @@ -6,7 +6,7 @@ import { getQueryFilter, getFilter } from './get_filter'; import { PartialFilter } from '../types'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; describe('get_filter', () => { let servicesMock: AlertServicesMock; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.ts index 3c226130faf25..1630192b3c03a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_filter.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { assertUnreachable } from '../../../utils/build_query'; import { Filter, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts index 6fc99ada16ece..a4ddec13ac513 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; import { getInputIndex } from './get_input_output_index'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts index 85e3eeac476e4..c001312fbf2f5 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts @@ -5,7 +5,7 @@ */ import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; export const getInputIndex = async ( services: AlertServices, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index a306a016b4205..163ed76d0c6c3 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -13,7 +13,7 @@ import { } from './__mocks__/es_results'; import { searchAfterAndBulkCreate } from './search_after_bulk_create'; import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; import uuid from 'uuid'; import { getListItemResponseMock } from '../../../../../lists/common/schemas/response/list_item_schema.mock'; import { listMock } from '../../../../../lists/server/mocks'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts index 59c685ec3e815..e44b82224d1ce 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AlertServices } from '../../../../../alerts/server'; import { ListClient } from '../../../../../lists/server'; -import { AlertServices } from '../../../../../alerting/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes, RuleAlertParams } from '../types'; import { Logger } from '../../../../../../../src/core/server'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 8e7034b006327..f94eb7006829e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -8,7 +8,7 @@ import moment from 'moment'; import { loggingServiceMock } from 'src/core/server/mocks'; import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; import { signalRulesAlertType } from './signal_rule_alert_type'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; import { ruleStatusServiceFactory } from './rule_status_service'; import { getGapBetweenRuns } from './utils'; import { RuleExecutorOptions } from './types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts index 265f986533134..8b9fb0574efe9 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -18,7 +18,7 @@ import { } from './__mocks__/es_results'; import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; import { singleBulkCreate, filterDuplicateRules } from './single_bulk_create'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; describe('singleBulkCreate', () => { const mockService: AlertServicesMock = alertsMock.createAlertServices(); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts index 39aecde470e0b..6f4d01ea73a79 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts @@ -6,7 +6,7 @@ import { countBy, isEmpty } from 'lodash'; import { performance } from 'perf_hooks'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { SignalSearchResponse, BulkResponse } from './types'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes } from '../types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts index 2aa42234460d8..50b0cb27990f8 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts @@ -10,7 +10,7 @@ import { sampleDocSearchResultsWithSortId, } from './__mocks__/es_results'; import { singleSearchAfter } from './single_search_after'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; describe('singleSearchAfter', () => { const mockService: AlertServicesMock = alertsMock.createAlertServices(); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts index a7086a4fb229e..409f374d7df1e 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts @@ -5,7 +5,7 @@ */ import { performance } from 'perf_hooks'; -import { AlertServices } from '../../../../../alerting/server'; +import { AlertServices } from '../../../../../alerts/server'; import { Logger } from '../../../../../../../src/core/server'; import { SignalSearchResponse } from './types'; import { buildEventsSearchQuery } from './build_events_query'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/types.ts index 32b13c5251a6b..90497b6e34cb4 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertType, State, AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertType, State, AlertExecutorOptions } from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleAlertParams, OutputRuleAlertRest } from '../types'; import { SearchResponse } from '../../types'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/utils.ts index 989c919244d65..f0ca08b73fac6 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/utils.ts @@ -7,7 +7,7 @@ import { createHash } from 'crypto'; import moment from 'moment'; import dateMath from '@elastic/datemath'; -import { parseDuration } from '../../../../../alerting/server'; +import { parseDuration } from '../../../../../alerts/server'; import { BulkResponse, BulkResponseErrorAggregation } from './types'; export const generateId = ( diff --git a/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.test.ts index d29d885f9797a..d07fa382e114a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { alertsClientMock } from '../../../../../alerting/server/mocks'; +import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { getResult, getFindResultWithMultiHits } from '../routes/__mocks__/request_responses'; import { INTERNAL_RULE_ID_KEY, INTERNAL_IDENTIFIER } from '../../../../common/constants'; import { readRawTags, readTags, convertTagsToSet, convertToTags, isTags } from './read_tags'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.ts b/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.ts index 003c852cb80af..2bb2b5ec47e2f 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/tags/read_tags.ts @@ -6,7 +6,7 @@ import { has } from 'lodash/fp'; import { INTERNAL_IDENTIFIER } from '../../../../common/constants'; -import { AlertsClient } from '../../../../../alerting/server'; +import { AlertsClient } from '../../../../../alerts/server'; import { findRules } from '../rules/find_rules'; export interface TagType { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/plugins/siem/server/lib/detection_engine/types.ts index f2026804da51a..53c8a9bf0a7e7 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/types.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/types.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ListsDefaultArraySchema } from '../../../common/detection_engine/schemas/types/lists_default_array'; import { CallAPIOptions } from '../../../../../../src/core/server'; import { Filter } from '../../../../../../src/plugins/data/server'; import { IRuleStatusAttributes } from './rules/types'; -import { ListsDefaultArraySchema } from './routes/schemas/types/lists_default_array'; import { RuleAlertAction, RuleType } from '../../../common/detection_engine/types'; export type PartialFilter = Partial; diff --git a/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts b/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts index 413acaa2d4b0a..9fdfc9ff7be0d 100644 --- a/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts +++ b/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; export const ALL_OTHERS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel', + 'xpack.siem.detectionEngine.alerts.histogram.allOthersGroupingLabel', { defaultMessage: 'All others', } diff --git a/x-pack/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts b/x-pack/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts index 48c6081e855a0..c16b73ff51b56 100644 --- a/x-pack/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts +++ b/x-pack/plugins/siem/server/lib/timeline/routes/import_timelines_route.ts @@ -7,6 +7,7 @@ import { extname } from 'path'; import { chunk, omit } from 'lodash/fp'; +import { importRulesSchema } from '../../../../common/detection_engine/schemas/response/import_rules_schema'; import { createPromiseFromStreams } from '../../../../../../../src/legacy/utils'; import { IRouter } from '../../../../../../../src/core/server'; @@ -16,7 +17,6 @@ import { SetupPlugins } from '../../../plugin'; import { ConfigType } from '../../../config'; import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; -import { importRulesSchema } from '../../detection_engine/routes/schemas/response/import_rules_schema'; import { validate } from '../../detection_engine/routes/rules/validate'; import { buildSiemResponse, diff --git a/x-pack/plugins/siem/server/plugin.ts b/x-pack/plugins/siem/server/plugin.ts index 5a47efd458888..a8858c91d677c 100644 --- a/x-pack/plugins/siem/server/plugin.ts +++ b/x-pack/plugins/siem/server/plugin.ts @@ -15,7 +15,7 @@ import { PluginInitializerContext, Logger, } from '../../../../src/core/server'; -import { PluginSetupContract as AlertingSetup } from '../../alerting/server'; +import { PluginSetupContract as AlertingSetup } from '../../alerts/server'; import { SecurityPluginSetup as SecuritySetup } from '../../security/server'; import { PluginSetupContract as FeaturesSetup } from '../../features/server'; import { MlPluginSetup as MlSetup } from '../../ml/server'; @@ -46,7 +46,7 @@ import { EndpointAppContext } from './endpoint/types'; import { IngestIndexPatternRetriever } from './endpoint/alerts/index_pattern'; export interface SetupPlugins { - alerting: AlertingSetup; + alerts: AlertingSetup; encryptedSavedObjects?: EncryptedSavedObjectsSetup; features: FeaturesSetup; licensing: LicensingPluginSetup; @@ -191,7 +191,7 @@ export class Plugin implements IPlugin { + return `${location.pathname}?${location.search}`; +}; + export const services = { uiMetricService: new UiMetricService('snapshot_restore'), httpService, i18n, + history, }; setUiMetricService(services.uiMetricService); diff --git a/x-pack/plugins/snapshot_restore/public/application/app.tsx b/x-pack/plugins/snapshot_restore/public/application/app.tsx index 8e57e79c08ee1..6174ff022b7ea 100644 --- a/x-pack/plugins/snapshot_restore/public/application/app.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/app.tsx @@ -17,7 +17,7 @@ import { NotAuthorizedSection, } from '../shared_imports'; import { SectionLoading } from './components'; -import { BASE_PATH, DEFAULT_SECTION, Section } from './constants'; +import { DEFAULT_SECTION, Section } from './constants'; import { RepositoryAdd, RepositoryEdit, @@ -63,34 +63,23 @@ export const App: React.FunctionComponent = () => { ) : hasPrivileges ? (
- + + - - + - {slmUi.enabled && ( - - )} - {slmUi.enabled && ( - - )} - + {slmUi.enabled && } + {slmUi.enabled && } + +
) : ( diff --git a/x-pack/plugins/snapshot_restore/public/application/app_context.tsx b/x-pack/plugins/snapshot_restore/public/application/app_context.tsx index 8ad05b3de5e98..d50f051c37fc0 100644 --- a/x-pack/plugins/snapshot_restore/public/application/app_context.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/app_context.tsx @@ -7,7 +7,7 @@ import React, { createContext, useContext } from 'react'; import { i18n } from '@kbn/i18n'; -import { CoreStart } from '../../../../../src/core/public'; +import { CoreStart, ScopedHistory } from '../../../../../src/core/public'; import { ClientConfigType } from '../types'; import { HttpService, UiMetricService } from './services'; @@ -19,6 +19,7 @@ export interface AppDependencies { httpService: HttpService; uiMetricService: UiMetricService; i18n: typeof i18n; + history: ScopedHistory; }; config: ClientConfigType; } diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx index 21e6bc6530707..8a7338f4db4e7 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx @@ -30,6 +30,8 @@ import { documentationLinksService } from '../../../services/documentation'; import { SectionLoading } from '../../'; import { StepProps } from './'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + export const PolicyStepLogistics: React.FunctionComponent = ({ policy, updatePolicy, @@ -50,7 +52,7 @@ export const PolicyStepLogistics: React.FunctionComponent = ({ sendRequest: reloadRepositories, } = useLoadRepositories(); - const { i18n } = useServices(); + const { i18n, history } = useServices(); // State for touched inputs const [touched, setTouched] = useState({ @@ -226,7 +228,7 @@ export const PolicyStepLogistics: React.FunctionComponent = ({ }} actions={ ( - +interface AppWithRouterProps { + history: ScopedHistory; +} + +const AppWithRouter = ({ history }: AppWithRouterProps) => ( + - + ); export const renderApp = (elem: Element, dependencies: AppDependencies) => { render( - + , elem ); diff --git a/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts b/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts index 9697e24a7147e..b06a60a5c830f 100644 --- a/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts +++ b/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts @@ -23,7 +23,7 @@ export async function mountManagementSection( config: ClientConfigType, params: ManagementAppMountParams ) { - const { element, setBreadcrumbs } = params; + const { element, setBreadcrumbs, history } = params; const [core] = await coreSetup.getStartServices(); const { docLinks, @@ -41,8 +41,15 @@ export async function mountManagementSection( httpService, uiMetricService: services.uiMetricService, i18n, + history, }, }; - return renderApp(element, appDependencies); + const unmountAppCallback = renderApp(element, appDependencies); + + return () => { + // Change tab label back to Kibana. + docTitle.reset(); + unmountAppCallback(); + }; } diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx index e1c20a76ba07c..5959ad6441f5d 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx @@ -41,6 +41,8 @@ import { } from '../../../../components'; import { TabSummary, TabHistory } from './tabs'; +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; + interface Props { policyName: SlmPolicy['name']; onClose: () => void; @@ -62,7 +64,7 @@ export const PolicyDetails: React.FunctionComponent = ({ onPolicyDeleted, onPolicyExecuted, }) => { - const { i18n, uiMetricService } = useServices(); + const { i18n, uiMetricService, history } = useServices(); const { error, data: policyDetails, sendRequest: reload } = useLoadPolicy(policyName); const [activeTab, setActiveTab] = useState(TAB_SUMMARY); const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -258,7 +260,7 @@ export const PolicyDetails: React.FunctionComponent = ({ } ), icon: 'pencil', - href: linkToEditPolicy(policyName), + ...reactRouterNavigate(history, linkToEditPolicy(policyName)), }, { name: i18n.translate( @@ -317,9 +319,12 @@ export const PolicyDetails: React.FunctionComponent = ({ diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_history.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_history.tsx index 22c37241348e7..d6f379045a472 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_history.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_history.tsx @@ -23,6 +23,9 @@ import { import { SlmPolicy } from '../../../../../../../common/types'; import { FormattedDateTime } from '../../../../../components'; import { linkToSnapshot } from '../../../../../services/navigation'; +import { useServices } from '../../../../../app_context'; + +import { reactRouterNavigate } from '../../../../../../../../../../src/plugins/kibana_react/public'; interface Props { policy: SlmPolicy; @@ -30,6 +33,7 @@ interface Props { export const TabHistory: React.FunctionComponent = ({ policy }) => { const { lastSuccess, lastFailure, nextExecutionMillis, name, repository } = policy; + const { history } = useServices(); const renderLastSuccess = () => { if (!lastSuccess) { @@ -73,7 +77,11 @@ export const TabHistory: React.FunctionComponent = ({ policy }) => { - {snapshotName} + + {snapshotName} +
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx index 053c4dc108e72..7bcee4f5f6621 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx @@ -25,12 +25,14 @@ import { useServices } from '../../../../../app_context'; import { FormattedDateTime, CollapsibleIndicesList } from '../../../../../components'; import { linkToSnapshots, linkToRepository } from '../../../../../services/navigation'; +import { reactRouterNavigate } from '../../../../../../../../../../src/plugins/kibana_react/public'; + interface Props { policy: SlmPolicy; } export const TabSummary: React.FunctionComponent = ({ policy }) => { - const { i18n } = useServices(); + const { i18n, history } = useServices(); const { version, @@ -180,7 +182,9 @@ export const TabSummary: React.FunctionComponent = ({ policy }) => { - {snapshotName} + + {snapshotName} + @@ -193,7 +197,9 @@ export const TabSummary: React.FunctionComponent = ({ policy }) => { - {repository} + + {repository} + diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx index 050274f1319b5..39ef66eb0658c 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx @@ -28,6 +28,8 @@ import { PolicyDetails } from './policy_details'; import { PolicyTable } from './policy_table'; import { PolicyRetentionSchedule } from './policy_retention_schedule'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + interface MatchParams { policyName?: SlmPolicy['name']; } @@ -130,7 +132,7 @@ export const PolicyList: React.FunctionComponent Promise>; @@ -47,7 +49,7 @@ export const PolicyTable: React.FunctionComponent = ({ onPolicyDeleted, onPolicyExecuted, }) => { - const { i18n, uiMetricService } = useServices(); + const { i18n, uiMetricService, history } = useServices(); const [selectedItems, setSelectedItems] = useState([]); const columns = [ @@ -64,8 +66,9 @@ export const PolicyTable: React.FunctionComponent = ({ {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} uiMetricService.trackUiMetric(UIM_POLICY_SHOW_DETAILS_CLICK)} - href={openPolicyDetailsUrl(name)} + {...reactRouterNavigate(history, openPolicyDetailsUrl(name), () => + uiMetricService.trackUiMetric(UIM_POLICY_SHOW_DETAILS_CLICK) + )} data-test-subj="policyLink" > {name} @@ -249,7 +252,7 @@ export const PolicyTable: React.FunctionComponent = ({ )} iconType="pencil" color="primary" - href={linkToEditPolicy(name)} + {...reactRouterNavigate(history, linkToEditPolicy(name))} data-test-subj="editPolicyButton" /> @@ -373,7 +376,7 @@ export const PolicyTable: React.FunctionComponent = ({ , void; @@ -62,7 +64,7 @@ export const RepositoryDetails: React.FunctionComponent = ({ onClose, onRepositoryDeleted, }) => { - const { i18n } = useServices(); + const { i18n, history } = useServices(); const { error, data: repositoryDetails } = useLoadRepository(repositoryName); const [verification, setVerification] = useState(undefined); const [cleanup, setCleanup] = useState(undefined); @@ -162,7 +164,7 @@ export const RepositoryDetails: React.FunctionComponent = ({ ); } return ( - + = ({ - + = ({ openRepositoryDetailsUrl, onRepositoryDeleted, }) => { - const { i18n, uiMetricService } = useServices(); + const { i18n, uiMetricService, history } = useServices(); const [selectedItems, setSelectedItems] = useState([]); const columns = [ @@ -55,8 +57,9 @@ export const RepositoryTable: React.FunctionComponent = ({ {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} uiMetricService.trackUiMetric(UIM_REPOSITORY_SHOW_DETAILS_CLICK)} - href={openRepositoryDetailsUrl(name)} + {...reactRouterNavigate(history, openRepositoryDetailsUrl(name), () => + uiMetricService.trackUiMetric(UIM_REPOSITORY_SHOW_DETAILS_CLICK) + )} data-test-subj="repositoryLink" > {name} @@ -116,7 +119,7 @@ export const RepositoryTable: React.FunctionComponent = ({ )} iconType="pencil" color="primary" - href={linkToEditRepository(name)} + {...reactRouterNavigate(history, linkToEditRepository(name))} data-test-subj="editRepositoryButton" /> @@ -247,7 +250,7 @@ export const RepositoryTable: React.FunctionComponent = ({ , { currentInterval ); - const { uiMetricService } = useServices(); + const { uiMetricService, history } = useServices(); // Track component loaded useEffect(() => { @@ -110,7 +112,7 @@ export const RestoreList: React.FunctionComponent = () => { defaultMessage="Go to {snapshotsLink} to start a restore." values={{ snapshotsLink: ( - + = ({ onClose, onSnapshotDeleted, }) => { - const { i18n, uiMetricService } = useServices(); + const { i18n, uiMetricService, history } = useServices(); const { error, data: snapshotDetails } = useLoadSnapshot(repositoryName, snapshotId); const [activeTab, setActiveTab] = useState(TAB_SUMMARY); @@ -221,7 +223,10 @@ export const SnapshotDetails: React.FunctionComponent = ({ = ({

- + = ({ snapshotDetails }) => { policyName, } = snapshotDetails; + const { history } = useServices(); + return ( @@ -201,7 +206,9 @@ export const TabSummary: React.FC = ({ snapshotDetails }) => { - {policyName} + + {policyName} + ) : null} diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx index 43bdd973f3d45..2b8df7294c374 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx @@ -27,6 +27,8 @@ import { useServices } from '../../../app_context'; import { SnapshotDetails } from './snapshot_details'; import { SnapshotTable } from './snapshot_table'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + interface MatchParams { repositoryName?: string; snapshotId?: string; @@ -141,7 +143,7 @@ export const SnapshotList: React.FunctionComponent +

{policies.length === 0 ? ( ) : ( + = ({ repositoryFilter, policyFilter, }) => { - const { i18n, uiMetricService } = useServices(); + const { i18n, uiMetricService, history } = useServices(); const [selectedItems, setSelectedItems] = useState([]); const lastSuccessfulManagedSnapshot = getLastSuccessfulManagedSnapshot(snapshots); @@ -74,8 +76,11 @@ export const SnapshotTable: React.FunctionComponent = ({ render: (snapshotId: string, snapshot: SnapshotDetails) => ( /* eslint-disable-next-line @elastic/eui/href-or-on-click */ uiMetricService.trackUiMetric(UIM_SNAPSHOT_SHOW_DETAILS_CLICK)} - href={openSnapshotDetailsUrl(snapshot.repository, snapshotId)} + {...reactRouterNavigate( + history, + openSnapshotDetailsUrl(snapshot.repository, snapshotId), + () => uiMetricService.trackUiMetric(UIM_SNAPSHOT_SHOW_DETAILS_CLICK) + )} data-test-subj="snapshotLink" > {snapshotId} @@ -90,7 +95,10 @@ export const SnapshotTable: React.FunctionComponent = ({ truncateText: true, sortable: true, render: (repositoryName: string) => ( - + {repositoryName} ), @@ -199,7 +207,7 @@ export const SnapshotTable: React.FunctionComponent = ({ iconType="importAction" color="primary" data-test-subj="srsnapshotListRestoreActionButton" - href={linkToRestoreSnapshot(repository, snapshot)} + {...reactRouterNavigate(history, linkToRestoreSnapshot(repository, snapshot))} isDisabled={!canRestore} /> diff --git a/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts b/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts index 6f95000726106..503704c6fe820 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts @@ -4,68 +4,60 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BASE_PATH } from '../../constants'; - export function linkToHome() { - return `#${BASE_PATH}`; + return `/`; } export function linkToRepositories() { - return `#${BASE_PATH}/repositories`; + return `/repositories`; } export function linkToRepository(repositoryName: string) { - return `#${BASE_PATH}/repositories/${encodeURIComponent(repositoryName)}`; + return `/repositories/${encodeURIComponent(repositoryName)}`; } export function linkToEditRepository(repositoryName: string) { - return `#${BASE_PATH}/edit_repository/${encodeURIComponent(repositoryName)}`; + return `/edit_repository/${encodeURIComponent(repositoryName)}`; } export function linkToAddRepository(redirect?: string) { - return `#${BASE_PATH}/add_repository${ - redirect ? `?redirect=${encodeURIComponent(redirect)}` : '' - }`; + return `/add_repository${redirect ? `?redirect=${encodeURIComponent(redirect)}` : ''}`; } export function linkToSnapshots(repositoryName?: string, policyName?: string) { if (repositoryName) { - return `#${BASE_PATH}/snapshots?repository=${encodeURIComponent(repositoryName)}`; + return `/snapshots?repository=${encodeURIComponent(repositoryName)}`; } if (policyName) { - return `#${BASE_PATH}/snapshots?policy=${encodeURIComponent(policyName)}`; + return `/snapshots?policy=${encodeURIComponent(policyName)}`; } - return `#${BASE_PATH}/snapshots`; + return `/snapshots`; } export function linkToSnapshot(repositoryName: string, snapshotName: string) { - return `#${BASE_PATH}/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent( - snapshotName - )}`; + return `/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`; } export function linkToRestoreSnapshot(repositoryName: string, snapshotName: string) { - return `#${BASE_PATH}/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent( - snapshotName - )}`; + return `/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`; } export function linkToPolicies() { - return `#${BASE_PATH}/policies`; + return `/policies`; } export function linkToPolicy(policyName: string) { - return `#${BASE_PATH}/policies/${encodeURIComponent(policyName)}`; + return `/policies/${encodeURIComponent(policyName)}`; } export function linkToEditPolicy(policyName: string) { - return `#${BASE_PATH}/edit_policy/${encodeURIComponent(policyName)}`; + return `/edit_policy/${encodeURIComponent(policyName)}`; } export function linkToAddPolicy() { - return `#${BASE_PATH}/add_policy`; + return `/add_policy`; } export function linkToRestoreStatus() { - return `#${BASE_PATH}/restore_status`; + return `/restore_status`; } diff --git a/x-pack/plugins/spaces/common/licensing/license_service.test.ts b/x-pack/plugins/spaces/common/licensing/license_service.test.ts index bac6401e5ecfc..0f6ef33421ca0 100644 --- a/x-pack/plugins/spaces/common/licensing/license_service.test.ts +++ b/x-pack/plugins/spaces/common/licensing/license_service.test.ts @@ -5,7 +5,7 @@ */ import { of } from 'rxjs'; -import { licensingMock } from '../../../licensing/public/mocks'; +import { licenseMock } from '../../../licensing/common/licensing.mock'; import { SpacesLicenseService } from './license_service'; import { LICENSE_TYPE, LicenseType } from '../../../licensing/common/types'; @@ -18,7 +18,7 @@ describe('license#isEnabled', function () { }); it('should indicate that Spaces is disabled when xpack is unavailable', () => { - const rawLicenseMock = licensingMock.createLicenseMock(); + const rawLicenseMock = licenseMock.createLicenseMock(); rawLicenseMock.isAvailable = false; const serviceSetup = new SpacesLicenseService().setup({ license$: of(rawLicenseMock), @@ -29,7 +29,7 @@ describe('license#isEnabled', function () { for (const level in LICENSE_TYPE) { if (isNaN(level as any)) { it(`should indicate that Spaces is enabled with a ${level} license`, () => { - const rawLicense = licensingMock.createLicense({ + const rawLicense = licenseMock.createLicense({ license: { status: 'active', type: level as LicenseType, diff --git a/x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts b/x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts index 2cf34e842ce33..15d141ccc328e 100644 --- a/x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts +++ b/x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts @@ -19,7 +19,7 @@ export const createSpacesFeatureCatalogueEntry = (): FeatureCatalogueEntry => { }), description: getSpacesFeatureDescription(), icon: 'spacesApp', - path: '/app/kibana#/management/kibana/spaces', + path: '/app/management/kibana/spaces', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }; diff --git a/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx b/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx index 38d8451b658a8..3ce24f35ec8de 100644 --- a/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx +++ b/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx @@ -8,8 +8,13 @@ import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; +import { ApplicationStart } from 'kibana/public'; -export const SecureSpaceMessage = () => { +interface SecureSpaceMessageProps { + getUrlForApp: ApplicationStart['getUrlForApp']; +} + +export const SecureSpaceMessage = (props: SecureSpaceMessageProps) => { const rolesLinkTextAriaLabel = i18n.translate( 'xpack.spaces.management.secureSpaceMessage.rolesLinkTextAriaLabel', { defaultMessage: 'Roles management page' } @@ -24,7 +29,10 @@ export const SecureSpaceMessage = () => { defaultMessage="Want to assign a role to a space? Go to {rolesLink}." values={{ rolesLink: ( - + { + const getUrlForApp = (appId: string) => appId; + it(`renders as expected`, () => { expect( shallowWithIntl( @@ -44,6 +46,7 @@ describe('EnabledFeatures', () => { space={space} securityEnabled={true} onChange={jest.fn()} + getUrlForApp={getUrlForApp} /> ) ).toMatchSnapshot(); @@ -58,6 +61,7 @@ describe('EnabledFeatures', () => { space={space} securityEnabled={true} onChange={changeHandler} + getUrlForApp={getUrlForApp} /> ); @@ -86,6 +90,7 @@ describe('EnabledFeatures', () => { space={space} securityEnabled={true} onChange={changeHandler} + getUrlForApp={getUrlForApp} /> ); diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx index 6f0462a6ddcc2..9fe3bac73eeb1 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx @@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, Fragment, ReactNode } from 'react'; +import { ApplicationStart } from 'kibana/public'; import { FeatureConfig } from '../../../../../../plugins/features/public'; import { Space } from '../../../../common/model/space'; import { getEnabledFeatures } from '../../lib/feature_utils'; @@ -19,6 +20,7 @@ interface Props { features: FeatureConfig[]; securityEnabled: boolean; onChange: (space: Partial) => void; + getUrlForApp: ApplicationStart['getUrlForApp']; } export class EnabledFeatures extends Component { @@ -136,7 +138,9 @@ export class EnabledFeatures extends Component { defaultMessage="Want to secure access? Go to {rolesLink}." values={{ rolesLink: ( - + { + const getUrlForApp = (appId: string) => appId; + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + it('allows a space to be created', async () => { const spacesManager = spacesManagerMock.create(); spacesManager.createSpace = jest.fn(spacesManager.createSpace); @@ -46,6 +51,8 @@ describe('ManageSpacePage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -103,6 +110,8 @@ describe('ManageSpacePage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -152,6 +161,8 @@ describe('ManageSpacePage', () => { getFeatures={() => Promise.reject(error)} notifications={notifications} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -187,6 +198,8 @@ describe('ManageSpacePage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -246,6 +259,8 @@ describe('ManageSpacePage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx index 9931b4d113df6..e725310c41817 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx @@ -18,7 +18,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import React, { Component, Fragment } from 'react'; -import { Capabilities, NotificationsStart } from 'src/core/public'; +import { ApplicationStart, Capabilities, NotificationsStart, ScopedHistory } from 'src/core/public'; import { Feature, FeaturesPluginStart } from '../../../../features/public'; import { isReservedSpace } from '../../../common'; import { Space } from '../../../common/model/space'; @@ -40,6 +40,8 @@ interface Props { onLoadSpace?: (space: Space) => void; capabilities: Capabilities; securityEnabled: boolean; + history: ScopedHistory; + getUrlForApp: ApplicationStart['getUrlForApp']; } interface State { @@ -154,6 +156,7 @@ export class ManageSpacePage extends Component { space={this.state.space} features={this.state.features} onChange={this.onSpaceChange} + getUrlForApp={this.props.getUrlForApp} securityEnabled={this.props.securityEnabled} /> @@ -195,7 +198,7 @@ export class ManageSpacePage extends Component { public maybeGetSecureSpacesMessage = () => { if (this.editingExistingSpace() && this.props.securityEnabled) { - return ; + return ; } return null; }; @@ -387,7 +390,9 @@ export class ManageSpacePage extends Component { } ) ); - window.location.hash = `#/management/kibana/spaces`; + + this.backToSpacesList(); + if (requireRefresh) { setTimeout(() => { window.location.reload(); @@ -408,9 +413,7 @@ export class ManageSpacePage extends Component { }); }; - private backToSpacesList = () => { - window.location.hash = `#/management/kibana/spaces`; - }; + private backToSpacesList = () => this.props.history.push('/'); private editingExistingSpace = () => !!this.props.spaceId; } diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap b/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap index aa6db7e22fd5d..200c9c55a4f7c 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap +++ b/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap @@ -39,7 +39,7 @@ exports[`SpacesGridPage renders as expected 1`] = ` > - +

`; diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx index d26b57ca7d80a..a98fae2561827 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Capabilities, NotificationsStart } from 'src/core/public'; +import { ApplicationStart, Capabilities, NotificationsStart, ScopedHistory } from 'src/core/public'; import { Feature, FeaturesPluginStart } from '../../../../features/public'; import { isReservedSpace } from '../../../common'; import { DEFAULT_SPACE_ID } from '../../../common/constants'; @@ -32,6 +32,7 @@ import { ConfirmDeleteModal } from '../components/confirm_delete_modal'; import { SecureSpaceMessage } from '../components/secure_space_message'; import { UnauthorizedPrompt } from '../components/unauthorized_prompt'; import { getEnabledFeatures } from '../lib/feature_utils'; +import { reactRouterNavigate } from '../../../../../../src/plugins/kibana_react/public'; interface Props { spacesManager: SpacesManager; @@ -39,6 +40,8 @@ interface Props { getFeatures: FeaturesPluginStart['getFeatures']; capabilities: Capabilities; securityEnabled: boolean; + history: ScopedHistory; + getUrlForApp: ApplicationStart['getUrlForApp']; } interface State { @@ -71,7 +74,9 @@ export class SpacesGridPage extends Component { return (
{this.getPageContent()} - {this.props.securityEnabled && } + {this.props.securityEnabled && ( + + )} {this.getConfirmDeleteModal()}
); @@ -135,7 +140,7 @@ export class SpacesGridPage extends Component { public getPrimaryActionButton() { return ( - + { name: '', width: '50px', render: (value: string, record: Space) => ( - + ), @@ -256,7 +261,9 @@ export class SpacesGridPage extends Component { }), sortable: true, render: (value: string, record: Space) => ( - {value} + + {value} + ), }, { @@ -336,7 +343,7 @@ export class SpacesGridPage extends Component { )} color={'primary'} iconType={'pencil'} - href={this.getEditSpacePath(record)} + {...reactRouterNavigate(this.props.history, this.getEditSpacePath(record))} /> ), }, @@ -362,9 +369,7 @@ export class SpacesGridPage extends Component { ]; } - private getEditSpacePath = (space: Space) => { - return `#/management/kibana/spaces/edit/${encodeURIComponent(space.id)}`; - }; + private getEditSpacePath = (space: Space) => `edit/${encodeURIComponent(space.id)}`; private onDeleteSpaceClick = (space: Space) => { this.setState({ diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx index 9b7dc921b9a25..1868823823a1a 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx @@ -5,12 +5,13 @@ */ import React from 'react'; +import { ScopedHistory } from 'kibana/public'; import { mountWithIntl, shallowWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { SpaceAvatar } from '../../space_avatar'; import { spacesManagerMock } from '../../spaces_manager/mocks'; import { SpacesManager } from '../../spaces_manager'; import { SpacesGridPage } from './spaces_grid_page'; -import { httpServiceMock } from 'src/core/public/mocks'; +import { httpServiceMock, scopedHistoryMock } from 'src/core/public/mocks'; import { notificationServiceMock } from 'src/core/public/mocks'; import { featuresPluginMock } from '../../../../features/public/mocks'; import { Feature } from '../../../../features/public'; @@ -52,6 +53,9 @@ featuresStart.getFeatures.mockResolvedValue([ ]); describe('SpacesGridPage', () => { + const getUrlForApp = (appId: string) => appId; + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + it('renders as expected', () => { const httpStart = httpServiceMock.createStartContract(); httpStart.get.mockResolvedValue([]); @@ -63,6 +67,8 @@ describe('SpacesGridPage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -84,6 +90,8 @@ describe('SpacesGridPage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -116,6 +124,8 @@ describe('SpacesGridPage', () => { getFeatures={featuresStart.getFeatures} notifications={notifications} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -149,6 +159,8 @@ describe('SpacesGridPage', () => { getFeatures={() => Promise.reject(error)} notifications={notifications} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 92c78d63d1b2e..834bfb73d8f46 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -17,16 +17,17 @@ jest.mock('./edit_space', () => ({ }, })); +import { ScopedHistory } from 'src/core/public'; import { spacesManagementApp } from './spaces_management_app'; -import { coreMock } from '../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../src/core/public/mocks'; import { securityMock } from '../../../security/public/mocks'; import { spacesManagerMock } from '../spaces_manager/mocks'; import { SecurityLicenseFeatures } from '../../../security/public'; import { featuresPluginMock } from '../../../features/public/mocks'; import { PluginsStart } from '../plugin'; -async function mountApp(basePath: string, spaceId?: string) { +async function mountApp(basePath: string, pathname: string, spaceId?: string) { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); @@ -53,7 +54,12 @@ async function mountApp(basePath: string, spaceId?: string) { securityLicense, getStartServices: async () => [coreStart, pluginsStart as PluginsStart, {}], }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -77,16 +83,13 @@ describe('spacesManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/spaces'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Spaces' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Spaces' }]); expect(container).toMatchInlineSnapshot(`
- Spaces Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"securityEnabled":true} + Spaces Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}},"securityEnabled":true}
`); @@ -96,19 +99,16 @@ describe('spacesManagementApp', () => { }); it('mount() works for the `create space` page', async () => { - const basePath = '/some-base-path/spaces'; - window.location.hash = `${basePath}/create`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/create'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Spaces' }, + { href: `/`, text: 'Spaces' }, { text: 'Create' }, ]); expect(container).toMatchInlineSnapshot(`
- Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"securityEnabled":true} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}},"securityEnabled":true}
`); @@ -118,20 +118,18 @@ describe('spacesManagementApp', () => { }); it('mount() works for the `edit space` page', async () => { - const basePath = '/some-base-path/spaces'; const spaceId = 'some-space'; - window.location.hash = `${basePath}/edit/${spaceId}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath, spaceId); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${spaceId}`, spaceId); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Spaces' }, - { href: `#/some-base-path/spaces/edit/${spaceId}`, text: `space with id some-space` }, + { href: `/`, text: 'Spaces' }, + { href: `/edit/${spaceId}`, text: `space with id some-space` }, ]); expect(container).toMatchInlineSnapshot(`
- Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"spaceId":"some-space","securityEnabled":true} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"securityEnabled":true}
`); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index 079cf2234b13b..5b8b993d96adc 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor } from 'src/core/public'; import { SecurityLicense } from '../../../security/public'; @@ -32,7 +32,7 @@ export const spacesManagementApp = Object.freeze({ title: i18n.translate('xpack.spaces.displayName', { defaultMessage: 'Spaces', }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { const [ { notifications, i18n: i18nStart, application }, { features }, @@ -42,7 +42,7 @@ export const spacesManagementApp = Object.freeze({ text: i18n.translate('xpack.spaces.management.breadcrumb', { defaultMessage: 'Spaces', }), - href: `#${basePath}`, + href: `/`, }, ]; @@ -54,6 +54,8 @@ export const spacesManagementApp = Object.freeze({ getFeatures={features.getFeatures} notifications={notifications} spacesManager={spacesManager} + history={history} + getUrlForApp={application.getUrlForApp} securityEnabled={securityLicense?.getFeatures().showLinks ?? false} /> ); @@ -75,6 +77,8 @@ export const spacesManagementApp = Object.freeze({ getFeatures={features.getFeatures} notifications={notifications} spacesManager={spacesManager} + history={history} + getUrlForApp={application.getUrlForApp} securityEnabled={securityLicense?.getFeatures().showLinks ?? false} /> ); @@ -88,7 +92,7 @@ export const spacesManagementApp = Object.freeze({ ...spacesBreadcrumbs, { text: space.name, - href: `#${basePath}/edit/${encodeURIComponent(space.id)}`, + href: `/edit/${encodeURIComponent(space.id)}`, }, ]); }; @@ -101,6 +105,8 @@ export const spacesManagementApp = Object.freeze({ spacesManager={spacesManager} spaceId={spaceId} onLoadSpace={onLoadSpace} + history={history} + getUrlForApp={application.getUrlForApp} securityEnabled={securityLicense?.getFeatures().showLinks ?? false} /> ); @@ -108,9 +114,9 @@ export const spacesManagementApp = Object.freeze({ render( - + - + diff --git a/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx b/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx index 456b7b0189a75..98467ebdb790b 100644 --- a/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx +++ b/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx @@ -46,6 +46,6 @@ export class ManageSpacesButton extends Component { this.props.onClick(); } - this.props.navigateToApp('kibana', { path: '#/management/kibana/spaces' }); + this.props.navigateToApp('management', { path: 'kibana/spaces' }); }; } diff --git a/x-pack/plugins/spaces/public/plugin.test.ts b/x-pack/plugins/spaces/public/plugin.test.ts index a98f593f546a0..4a49cf20d3a4a 100644 --- a/x-pack/plugins/spaces/public/plugin.test.ts +++ b/x-pack/plugins/spaces/public/plugin.test.ts @@ -33,17 +33,11 @@ describe('Spaces plugin', () => { it('should register the management and feature catalogue sections when the management and home plugins are both available', () => { const coreSetup = coreMock.createSetup(); - const kibanaSection = new ManagementSection( - { - id: ManagementSectionId.Kibana, - title: 'Mock Kibana Section', - order: 1, - }, - jest.fn(), - jest.fn(), - jest.fn(), - coreSetup.getStartServices - ); + const kibanaSection = new ManagementSection({ + id: ManagementSectionId.Kibana, + title: 'Mock Kibana Section', + order: 1, + }); const registerAppSpy = jest.spyOn(kibanaSection, 'registerApp'); diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx index 01ff7f5bff27f..ccfdc8b0942fa 100644 --- a/x-pack/plugins/transform/public/app/app.tsx +++ b/x-pack/plugins/transform/public/app/app.tsx @@ -6,7 +6,8 @@ import React, { useContext, FC } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter, Redirect, Route, Switch } from 'react-router-dom'; +import { Router, Route, Switch } from 'react-router-dom'; +import { ScopedHistory } from 'kibana/public'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -15,7 +16,7 @@ import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/p import { API_BASE_PATH } from '../../common/constants'; import { SectionError } from './components'; -import { CLIENT_BASE_PATH, SECTION_SLUG } from './constants'; +import { SECTION_SLUG } from './constants'; import { AuthorizationContext, AuthorizationProvider } from './lib/authorization'; import { AppDependencies } from './app_dependencies'; @@ -23,7 +24,7 @@ import { CloneTransformSection } from './sections/clone_transform'; import { CreateTransformSection } from './sections/create_transform'; import { TransformManagementSection } from './sections/transform_management'; -export const App: FC = () => { +export const App: FC<{ history: ScopedHistory }> = ({ history }) => { const { apiError } = useContext(AuthorizationContext); if (apiError !== null) { return ( @@ -41,24 +42,19 @@ export const App: FC = () => { return (
- + - - + - +
); }; @@ -70,7 +66,7 @@ export const renderApp = (element: HTMLElement, appDependencies: AppDependencies - + , diff --git a/x-pack/plugins/transform/public/app/app_dependencies.tsx b/x-pack/plugins/transform/public/app/app_dependencies.tsx index 87db02988adf0..704875330fdd2 100644 --- a/x-pack/plugins/transform/public/app/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/app_dependencies.tsx @@ -6,6 +6,7 @@ import { CoreSetup, CoreStart } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { ScopedHistory } from 'kibana/public'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; @@ -21,6 +22,7 @@ export interface AppDependencies { savedObjects: CoreStart['savedObjects']; storage: Storage; overlays: CoreStart['overlays']; + history: ScopedHistory; } export const useAppDependencies = () => { diff --git a/x-pack/plugins/transform/public/app/common/navigation.test.tsx b/x-pack/plugins/transform/public/app/common/navigation.test.tsx index 97cb5b4d5d236..4fd4376a021fc 100644 --- a/x-pack/plugins/transform/public/app/common/navigation.test.tsx +++ b/x-pack/plugins/transform/public/app/common/navigation.test.tsx @@ -9,7 +9,7 @@ import { getDiscoverUrl } from './navigation'; describe('navigation', () => { test('getDiscoverUrl should provide encoded url to Discover page', () => { expect(getDiscoverUrl('farequote-airline', 'http://example.com')).toBe( - 'http://example.com#/discover?_g=()&_a=(index:farequote-airline)' + 'http://example.com/app/discover#?_g=()&_a=(index:farequote-airline)' ); }); }); diff --git a/x-pack/plugins/transform/public/app/common/navigation.tsx b/x-pack/plugins/transform/public/app/common/navigation.tsx index 15966a93e1f42..c43a12ec3824b 100644 --- a/x-pack/plugins/transform/public/app/common/navigation.tsx +++ b/x-pack/plugins/transform/public/app/common/navigation.tsx @@ -8,7 +8,7 @@ import React, { FC } from 'react'; import { Redirect } from 'react-router-dom'; import rison from 'rison-node'; -import { CLIENT_BASE_PATH, SECTION_SLUG } from '../constants'; +import { SECTION_SLUG } from '../constants'; /** * Gets a url for navigating to Discover page. @@ -23,15 +23,13 @@ export function getDiscoverUrl(indexPatternId: string, baseUrl: string): string index: indexPatternId, }); - const hash = `#/discover?_g=${_g}&_a=${_a}`; + const hash = `/discover#?_g=${_g}&_a=${_a}`; - return `${baseUrl}${hash}`; + return `${baseUrl}/app${hash}`; } -export const RedirectToTransformManagement: FC = () => ( - -); +export const RedirectToTransformManagement: FC = () => ; export const RedirectToCreateTransform: FC<{ savedObjectId: string }> = ({ savedObjectId }) => ( - + ); diff --git a/x-pack/plugins/transform/public/app/constants/index.ts b/x-pack/plugins/transform/public/app/constants/index.ts index 3156fae7545b1..299007d84768f 100644 --- a/x-pack/plugins/transform/public/app/constants/index.ts +++ b/x-pack/plugins/transform/public/app/constants/index.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CLIENT_BASE_PATH = '/management/data/transform/'; - export enum SECTION_SLUG { HOME = 'transform_management', CLONE_TRANSFORM = 'clone_transform', diff --git a/x-pack/plugins/transform/public/app/mount_management_section.ts b/x-pack/plugins/transform/public/app/mount_management_section.ts index f3a48975a68e6..454738f7a313a 100644 --- a/x-pack/plugins/transform/public/app/mount_management_section.ts +++ b/x-pack/plugins/transform/public/app/mount_management_section.ts @@ -21,7 +21,7 @@ export async function mountManagementSection( coreSetup: CoreSetup, params: ManagementAppMountParams ) { - const { element, setBreadcrumbs } = params; + const { element, setBreadcrumbs, history } = params; const { http, notifications, getStartServices } = coreSetup; const startServices = await getStartServices(); const [core, plugins] = startServices; @@ -46,6 +46,7 @@ export async function mountManagementSection( savedObjects, storage: localStorage, uiSettings, + history, }; return renderApp(element, appDependencies); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx index 3fcfd77ba54cd..69fcf822de5d2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx @@ -120,14 +120,23 @@ export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) // as it was when transforms were part of the ML plugin. This will be revisited // to come up with an approach that's more in line with the overall layout // of the Kibana management section. - const managementBody = document.getElementsByClassName( + let managementBody = document.getElementsByClassName( KBN_MANAGEMENT_PAGE_CLASSNAME.DEFAULT_BODY ); if (managementBody.length > 0) { - managementBody[0].classList.add(KBN_MANAGEMENT_PAGE_CLASSNAME.TRANSFORM_BODY_MODIFIER); + managementBody[0].classList.replace( + KBN_MANAGEMENT_PAGE_CLASSNAME.DEFAULT_BODY, + KBN_MANAGEMENT_PAGE_CLASSNAME.TRANSFORM_BODY_MODIFIER + ); return () => { - managementBody[0].classList.remove(KBN_MANAGEMENT_PAGE_CLASSNAME.TRANSFORM_BODY_MODIFIER); + managementBody = document.getElementsByClassName( + KBN_MANAGEMENT_PAGE_CLASSNAME.TRANSFORM_BODY_MODIFIER + ); + managementBody[0].classList.replace( + KBN_MANAGEMENT_PAGE_CLASSNAME.TRANSFORM_BODY_MODIFIER, + KBN_MANAGEMENT_PAGE_CLASSNAME.DEFAULT_BODY + ); }; } }, []); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx index 4b333f73f048c..48188fd70bd7e 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx @@ -14,7 +14,7 @@ import { AuthorizationContext, } from '../../../../lib/authorization'; -import { CLIENT_BASE_PATH, SECTION_SLUG } from '../../../../constants'; +import { SECTION_SLUG } from '../../../../constants'; interface CloneActionProps { itemId: string; @@ -30,7 +30,7 @@ export const CloneAction: FC = ({ itemId }) => { }); function clickHandler() { - history.push(`${CLIENT_BASE_PATH}${SECTION_SLUG.CLONE_TRANSFORM}/${itemId}`); + history.push(`/${SECTION_SLUG.CLONE_TRANSFORM}/${itemId}`); } const cloneButton = ( diff --git a/x-pack/plugins/transform/public/app/services/navigation/breadcrumb.ts b/x-pack/plugins/transform/public/app/services/navigation/breadcrumb.ts index 6637b8a39cd56..7d4ab5d8163e1 100644 --- a/x-pack/plugins/transform/public/app/services/navigation/breadcrumb.ts +++ b/x-pack/plugins/transform/public/app/services/navigation/breadcrumb.ts @@ -5,7 +5,6 @@ */ import { textService } from '../text'; -import { linkToHome } from './links'; import { ManagementAppMountParams } from '../../../../../../../src/plugins/management/public'; @@ -41,7 +40,7 @@ class BreadcrumbService { this.breadcrumbs.home = [ { text: textService.breadcrumbs.home, - href: linkToHome(), + href: '/', }, ]; this.breadcrumbs.cloneTransform = [ diff --git a/x-pack/plugins/transform/public/app/services/navigation/index.ts b/x-pack/plugins/transform/public/app/services/navigation/index.ts index 40a6d6d9fbdc4..f1bd864f538ad 100644 --- a/x-pack/plugins/transform/public/app/services/navigation/index.ts +++ b/x-pack/plugins/transform/public/app/services/navigation/index.ts @@ -6,4 +6,3 @@ export { breadcrumbService, BREADCRUMB_SECTION } from './breadcrumb'; export { docTitleService } from './doc_title'; -export * from './links'; diff --git a/x-pack/plugins/transform/public/app/services/navigation/links.ts b/x-pack/plugins/transform/public/app/services/navigation/links.ts deleted file mode 100644 index 85088c3a4a69d..0000000000000 --- a/x-pack/plugins/transform/public/app/services/navigation/links.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CLIENT_BASE_PATH } from '../../constants'; - -export function linkToHome() { - return `#${CLIENT_BASE_PATH}`; -} diff --git a/x-pack/plugins/transform/public/register_feature.ts b/x-pack/plugins/transform/public/register_feature.ts index c81a18a3def87..796fa370dab25 100644 --- a/x-pack/plugins/transform/public/register_feature.ts +++ b/x-pack/plugins/transform/public/register_feature.ts @@ -22,7 +22,7 @@ export const registerFeature = (home: HomePublicPluginSetup) => { 'Use transforms to pivot existing Elasticsearch indices into summarized or entity-centric indices.', }), icon: 'managementApp', // there is currently no Transforms icon, so using the general management app icon - path: '/app/kibana#/management/data/transform', + path: '/app/management/data/transform', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4522a641d6dd9..e1481e18a2447 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -168,7 +168,6 @@ "common.ui.flotCharts.thuLabel": "木", "common.ui.flotCharts.tueLabel": "火", "common.ui.flotCharts.wedLabel": "水", - "common.ui.stackManagement.breadcrumb": "スタック管理", "common.ui.stateManagement.unableToParseUrlErrorMessage": "URL をパースできません", "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", @@ -565,7 +564,6 @@ "data.filter.filterBar.filterItemBadgeIconAriaLabel": "削除", "data.filter.filterBar.includeFilterButtonLabel": "結果を含める", "data.filter.filterBar.indexPatternSelectPlaceholder": "インデックスパターンの選択", - "data.filter.filterBar.labelErrorMessage": "フィルターを表示できませんでした", "data.filter.filterBar.labelErrorText": "エラー", "data.filter.filterBar.moreFilterActionsMessage": "フィルター:{innerText}。他のフィルターアクションを使用するには選択してください。", "data.filter.filterBar.negatedFilterPrefix": "NOT ", @@ -2179,10 +2177,6 @@ "kbn.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "デフォルトの WMS プロパティ", "kbn.advancedSettings.visualizeEnableLabsText": "ユーザーが実験的なビジュアライゼーションを作成、表示、編集できるようになります。無効の場合、\n ユーザーは本番準備が整ったビジュアライゼーションのみを利用できます。", "kbn.advancedSettings.visualizeEnableLabsTitle": "実験的なビジュアライゼーションを有効にする", - "kbn.management.landing.header": "Stack Management {version}へようこそ", - "kbn.management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibana の設定、その他を管理します。", - "kbn.management.landing.text": "アプリの一覧は左側のメニューにあります。", - "kbn.managementTitle": "スタック管理", "kibana_legacy.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。", "kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定", "kibana_legacy.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります", @@ -2224,9 +2218,11 @@ "kibana-react.tableListView.listing.table.editActionName": "編集", "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "{entityName} を削除できません", "management.breadcrumb": "スタック管理", - "management.displayName": "スタック管理", "management.nav.label": "管理", "management.nav.menu": "管理メニュー", + "management.landing.header": "Stack Management {version}へようこそ", + "management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibana の設定、その他を管理します。", + "management.landing.text": "アプリの一覧は左側のメニューにあります。", "management.stackManagement.managementDescription": "Elastic Stack の管理を行うセンターコンソールです。", "management.stackManagement.managementLabel": "スタック管理", "maps_legacy.baseMapsVisualization.childShouldImplementMethodErrorMessage": "子はdata-updateに対応できるようこのメソッドを導入する必要があります", @@ -3983,16 +3979,16 @@ "xpack.advancedUiActions.customizePanelTimeRange.modal.updatePanelTimeRangeButtonTitle": "更新", "xpack.advancedUiActions.customizeTimeRange.modal.headerTitle": "パネルの時間範囲のカスタマイズ", "xpack.advancedUiActions.customizeTimeRangeMenuItem.displayName": "時間範囲のカスタマイズ", - "xpack.alerting.alertNavigationRegistry.get.missingNavigationError": "「{consumer}」内のアラートタイプ「{alertType}」のナビゲーションは登録されていません。", - "xpack.alerting.alertNavigationRegistry.register.duplicateDefaultError": "「{consumer}」内のデフォルトナビゲーションは既に登録されています。", - "xpack.alerting.alertNavigationRegistry.register.duplicateNavigationError": "「{consumer}」内のアラートタイプ「{alertType}」のナビゲーションは既に登録されています。", - "xpack.alerting.alertsClient.validateActions.invalidGroups": "無効なアクショングループ: {groups}", - "xpack.alerting.alertTypeRegistry.get.missingAlertTypeError": "アラートタイプ\"{id}\"は登録されていません。", - "xpack.alerting.alertTypeRegistry.register.duplicateAlertTypeError": "アラートタイプ\"{id}\"は既に登録されています。", - "xpack.alerting.api.error.disabledApiKeys": "アラートは API キーに依存しますがキーが無効になっているようです", - "xpack.alerting.appName": "アラート", - "xpack.alerting.loadAlertType.missingAlertTypeError": "アラートタイプ「{id}」は登録されていません。", - "xpack.alerting.serverSideErrors.unavailableLicenseInformationErrorMessage": "アラートを利用できません。現在ライセンス情報が利用できません。", + "xpack.alerts.alertNavigationRegistry.get.missingNavigationError": "「{consumer}」内のアラートタイプ「{alertType}」のナビゲーションは登録されていません。", + "xpack.alerts.alertNavigationRegistry.register.duplicateDefaultError": "「{consumer}」内のデフォルトナビゲーションは既に登録されています。", + "xpack.alerts.alertNavigationRegistry.register.duplicateNavigationError": "「{consumer}」内のアラートタイプ「{alertType}」のナビゲーションは既に登録されています。", + "xpack.alerts.alertsClient.validateActions.invalidGroups": "無効なアクショングループ: {groups}", + "xpack.alerts.alertTypeRegistry.get.missingAlertTypeError": "アラートタイプ\"{id}\"は登録されていません。", + "xpack.alerts.alertTypeRegistry.register.duplicateAlertTypeError": "アラートタイプ\"{id}\"は既に登録されています。", + "xpack.alerts.api.error.disabledApiKeys": "アラートは API キーに依存しますがキーが無効になっているようです", + "xpack.alerts.appName": "アラート", + "xpack.alerts.loadAlertType.missingAlertTypeError": "アラートタイプ「{id}」は登録されていません。", + "xpack.alerts.serverSideErrors.unavailableLicenseInformationErrorMessage": "アラートを利用できません。現在ライセンス情報が利用できません。", "xpack.alertingBuiltins.indexThreshold.actionGroupThresholdMetTitle": "しきい値一致", "xpack.alertingBuiltins.indexThreshold.actionVariableContextDateLabel": "アラートがしきい値を超えた日付。", "xpack.alertingBuiltins.indexThreshold.actionVariableContextGroupLabel": "しきい値を超えたグループ。", @@ -4222,7 +4218,7 @@ "xpack.apm.jvmsTable.nonHeapMemoryColumnLabel": "非ヒープ領域の平均", "xpack.apm.jvmsTable.threadCountColumnLabel": "最大スレッド数", "xpack.apm.kueryBar.disabledPlaceholder": "サービスマップの検索は利用できません", - "xpack.apm.kueryBar.placeholder": "検索 {event, select,\n トランザクション {transactions}\n メトリック: {metric}\n エラー {errors}\n その他 {transactions, errors and metrics}\n } (E.g. {queryExample})", + "xpack.apm.kueryBar.placeholder": "検索 {event, select,\n transaction {トランザクション}\n metric {メトリック}\n error {エラー}\n other {その他}\n } (E.g. {queryExample})", "xpack.apm.license.betaBadge": "ベータ", "xpack.apm.license.betaTooltipMessage": "現在、この機能はベータです。不具合を見つけた場合やご意見がある場合、サポートに問い合わせるか、またはディスカッションフォーラムにご報告ください。", "xpack.apm.license.button": "トライアルを開始", @@ -8116,16 +8112,13 @@ "xpack.ingestManager.agentConfigForm.nameRequiredErrorMessage": "エージェント構成名が必要です", "xpack.ingestManager.agentConfigForm.namespaceFieldDescription": "この構成を使用するデータソースにデフォルトの名前空間を適用します。データソースはその独自の名前空間を指定できます。", "xpack.ingestManager.agentConfigForm.namespaceFieldLabel": "デフォルト名前空間", - "xpack.ingestManager.agentConfigForm.namespaceUseDefaultsFieldLabel": "デフォルト名前空間を使用する", "xpack.ingestManager.agentConfigForm.systemMonitoringFieldLabel": "オプション", "xpack.ingestManager.agentConfigForm.systemMonitoringText": "システムメトリックを収集", "xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText": "このオプションを有効にすると、システムメトリックと情報を収集するデータソースで構成をブートストラップできます。", "xpack.ingestManager.agentConfigList.actionsColumnTitle": "アクション", - "xpack.ingestManager.agentConfigList.actionsMenuText": "開く", "xpack.ingestManager.agentConfigList.addButton": "エージェント構成を作成", "xpack.ingestManager.agentConfigList.agentsColumnTitle": "エージェント", "xpack.ingestManager.agentConfigList.clearFiltersLinkText": "フィルターを消去", - "xpack.ingestManager.agentConfigList.createDatasourceActionText": "データソースを作成", "xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle": "データソース", "xpack.ingestManager.agentConfigList.descriptionColumnTitle": "説明", "xpack.ingestManager.agentConfigList.loadingAgentConfigsMessage": "エージェント構成を読み込み中...", @@ -8137,7 +8130,6 @@ "xpack.ingestManager.agentConfigList.reloadAgentConfigsButtonText": "再読み込み", "xpack.ingestManager.agentConfigList.revisionNumber": "rev. {revNumber}", "xpack.ingestManager.agentConfigList.updatedOnColumnTitle": "最終更新日:", - "xpack.ingestManager.agentConfigList.viewConfigActionText": "構成を表示", "xpack.ingestManager.agentDetails.actionsButton": "アクション", "xpack.ingestManager.agentDetails.agentConfigurationLabel": "エージェント構成", "xpack.ingestManager.agentDetails.agentDetailsTitle": "エージェント'{id}'", @@ -8198,7 +8190,6 @@ "xpack.ingestManager.agentHealth.onlineStatusText": "オンライン", "xpack.ingestManager.agentHealth.warningStatusText": "エラー", "xpack.ingestManager.agentList.actionsColumnTitle": "アクション", - "xpack.ingestManager.agentList.actionsMenuText": "開く", "xpack.ingestManager.agentList.addButton": "新しいエージェントを登録", "xpack.ingestManager.agentList.clearFiltersLinkText": "フィルターを消去", "xpack.ingestManager.agentList.configColumnTitle": "構成", @@ -8266,7 +8257,6 @@ "xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "ストリーム", "xpack.ingestManager.configDetails.subTabs.datasourcesTabText": "データソース", "xpack.ingestManager.configDetails.subTabs.settingsTabText": "設定", - "xpack.ingestManager.configDetails.subTabs.yamlTabText": "YAML", "xpack.ingestManager.configDetails.summary.datasources": "データソース", "xpack.ingestManager.configDetails.summary.lastUpdated": "最終更新日:", "xpack.ingestManager.configDetails.summary.revision": "リビジョン", @@ -13858,9 +13848,6 @@ "xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription": "Elasticから事前にパッケージ化されているルールをインストールすることができませんでした", "xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "Elasticから事前にパッケージ化されているルールをインストールしました", "xpack.siem.containers.detectionEngine.rules": "ルールを取得できませんでした", - "xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription": "シグナルをクエリできませんでした", - "xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription": "シグナルインデックス名を取得できませんでした", - "xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription": "シグナルインデックスを作成できませんでした", "xpack.siem.containers.detectionEngine.tagFetchFailDescription": "タグを取得できませんでした", "xpack.siem.containers.errors.dataFetchFailureTitle": "データの取得に失敗", "xpack.siem.containers.errors.networkFailureTitle": "ネットワーク障害", @@ -13977,7 +13964,6 @@ "xpack.siem.detectionEngine.detectionsPageTitle": "検出", "xpack.siem.detectionEngine.dismissButton": "閉じる", "xpack.siem.detectionEngine.dismissNoApiIntegrationKeyButton": "閉じる", - "xpack.siem.detectionEngine.dismissNoWriteSignalButton": "閉じる", "xpack.siem.detectionEngine.editRule.backToDescription": "戻る", "xpack.siem.detectionEngine.editRule.cancelTitle": "キャンセル", "xpack.siem.detectionEngine.editRule.errorMsgDescription": "申し訳ありません", @@ -13989,7 +13975,6 @@ "xpack.siem.detectionEngine.goToDocumentationButton": "ドキュメンテーションを表示", "xpack.siem.detectionEngine.headerPage.pageBadgeLabel": "ベータ", "xpack.siem.detectionEngine.headerPage.pageBadgeTooltip": "検出はまだベータ段階です。Kibana repoで問題やバグを報告して、製品の改善にご協力ください。", - "xpack.siem.detectionEngine.lastSignalTitle": "前回のシグナル", "xpack.siem.detectionEngine.mitreAttack.addTitle": "MITRE ATT&CK\\u2122脅威を追加", "xpack.siem.detectionEngine.mitreAttack.tacticPlaceHolderDescription": "Tacticを追加...", "xpack.siem.detectionEngine.mitreAttack.tacticsDescription": "Tactic", @@ -14279,8 +14264,6 @@ "xpack.siem.detectionEngine.noApiIntegrationKeyCallOutTitle": "API統合キーが必要です", "xpack.siem.detectionEngine.noIndexMsgBody": "検出エンジンを使用するには、必要なクラスターとインデックス権限のユーザーが最初にこのページにアクセスする必要があります。ヘルプについては、管理者にお問い合わせください。", "xpack.siem.detectionEngine.noIndexTitle": "検出エンジンを設定しましょう", - "xpack.siem.detectionEngine.noWriteSignalsCallOutMsg": "現在、シグナルを更新するための必要な権限がありません。サポートについては、管理者にお問い合わせください。", - "xpack.siem.detectionEngine.noWriteSignalsCallOutTitle": "シグナルインデックス権限が必要です", "xpack.siem.detectionEngine.pageTitle": "検出エンジン", "xpack.siem.detectionEngine.panelSubtitleShowing": "表示中", "xpack.siem.detectionEngine.readOnlyCallOutMsg": "現在、検出エンジンルールを作成/編集するための必要な権限がありません。サポートについては、管理者にお問い合わせください。", @@ -14385,43 +14368,6 @@ "xpack.siem.detectionEngine.ruleStatus.statusDateDescription": "ステータス日付", "xpack.siem.detectionEngine.ruleStatus.statusDescription": "前回の応答", "xpack.siem.detectionEngine.signalRuleAlert.actionGroups.default": "デフォルト", - "xpack.siem.detectionEngine.signals.actions.closeSignalTitle": "シグナルを閉じる", - "xpack.siem.detectionEngine.signals.actions.investigateInTimelineTitle": "タイムラインで調査", - "xpack.siem.detectionEngine.signals.actions.openSignalTitle": "シグナルを開く", - "xpack.siem.detectionEngine.signals.closedSignalsTitle": "閉じたシグナル", - "xpack.siem.detectionEngine.signals.documentTypeTitle": "シグナル", - "xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel": "その他すべて", - "xpack.siem.detectionEngine.signals.histogram.headerTitle": "シグナル数", - "xpack.siem.detectionEngine.signals.histogram.showingSignalsTitle": "表示中: {modifier}{totalSignalsFormatted} {totalSignals, plural, =1 {シグナル} other {シグナル}}", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.destinationIpsDropDown": "上位のデスティネーションIP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventActionsDropDown": "上位のイベントアクション", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventCategoriesDropDown": "上位のイベントカテゴリー", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.hostNamesDropDown": "上位のホスト名", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.riskScoresDropDown": "リスクスコア", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.rulesDropDown": "上位のルール", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.ruleTypesDropDown": "上位のルールタイプ", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.severitiesDropDown": "重要度", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.sourceIpsDropDown": "上位のソースIP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.stackByLabel": "積み上げ", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.usersDropDown": "上位のユーザー", - "xpack.siem.detectionEngine.signals.histogram.topNLabel": "トップ{fieldName}", - "xpack.siem.detectionEngine.signals.histogram.viewSignalsButtonLabel": "シグナルの表示", - "xpack.siem.detectionEngine.signals.loadingSignalsTitle": "シグナルの読み込み中", - "xpack.siem.detectionEngine.signals.openSignalsTitle": "シグナルを開く", - "xpack.siem.detectionEngine.signals.tableTitle": "シグナル", - "xpack.siem.detectionEngine.signals.totalCountOfSignalsTitle": "シグナルが検索条件に一致します", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.closeSelectedTitle": "選択した項目を閉じる", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.openSelectedTitle": "選択した項目を開く", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInHostsTitle": "ホストで選択した項目を表示", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInNetworkTitle": "ネットワークで選択した項目を表示", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInTimelineTitle": "タイムラインで選択した項目を表示", - "xpack.siem.detectionEngine.signals.utilityBar.batchActionsTitle": "バッチ処理", - "xpack.siem.detectionEngine.signals.utilityBar.clearSelectionTitle": "選択した項目をクリア", - "xpack.siem.detectionEngine.signals.utilityBar.selectAllSignalsTitle": "すべての{totalSignalsFormatted} {totalSignals, plural, =1 {シグナル} other {シグナル}}を選択", - "xpack.siem.detectionEngine.signals.utilityBar.selectedSignalsTitle": "{selectedSignalsFormatted} {selectedSignals, plural, =1 {シグナル} other {シグナル}}を選択しました", - "xpack.siem.detectionEngine.signals.utilityBar.showingSignalsTitle": "{totalSignalsFormatted} {totalSignals, plural, =1 {シグナル} other {シグナル}}を表示中", - "xpack.siem.detectionEngine.signalTitle": "検出したシグナル", - "xpack.siem.detectionEngine.totalSignalTitle": "合計", "xpack.siem.detectionEngine.userUnauthenticatedMsgBody": "検出エンジンを表示するための必要なアクセス権がありません。ヘルプについては、管理者にお問い合わせください。", "xpack.siem.detectionEngine.userUnauthenticatedTitle": "検出エンジンアクセス権が必要です", "xpack.siem.dragAndDrop.addToTimeline": "タイムライン調査に追加", @@ -14461,11 +14407,6 @@ "xpack.siem.eventsViewer.eventsLabel": "イベント", "xpack.siem.eventsViewer.footer.loadingEventsDataLabel": "イベントを読み込み中", "xpack.siem.eventsViewer.showingLabel": "表示中", - "xpack.siem.eventsViewer.signals.defaultHeaders.methodTitle": "メソド", - "xpack.siem.eventsViewer.signals.defaultHeaders.riskScoreTitle": "リスクスコア", - "xpack.siem.eventsViewer.signals.defaultHeaders.ruleTitle": "ルール", - "xpack.siem.eventsViewer.signals.defaultHeaders.severityTitle": "深刻度", - "xpack.siem.eventsViewer.signals.defaultHeaders.versionTitle": "バージョン", "xpack.siem.eventsViewer.unit": "{totalCount, plural, =1 {event} other {events}}", "xpack.siem.featureCatalogue.description": "セキュリティメトリクスとログのイベントとアラートを確認します", "xpack.siem.featureCatalogue.title": "SIEM", @@ -14798,7 +14739,6 @@ "xpack.siem.overview.recentlyCreatedCasesButtonLabel": "最近作成したケース", "xpack.siem.overview.recentTimelinesSidebarTitle": "最近のタイムライン", "xpack.siem.overview.showTopTooltip": "上位の{fieldName}を表示", - "xpack.siem.overview.signalCountTitle": "シグナル数", "xpack.siem.overview.startedText": "セキュリティ情報およびイベント管理(SIEM)へようこそ。はじめに{docs}や{data}をご参照ください。今後の機能に関する情報やチュートリアルは、{siemSolution} ページをお見逃しなく。", "xpack.siem.overview.startedText.dataLinkText": "投入データ", "xpack.siem.overview.startedText.docsLinkText": "ドキュメンテーション", @@ -14979,7 +14919,6 @@ "xpack.siem.topN.allEventsSelectLabel": "すべてのイベント", "xpack.siem.topN.closeButtonLabel": "閉じる", "xpack.siem.topN.rawEventsSelectLabel": "未加工イベント", - "xpack.siem.topN.signalEventsSelectLabel": "シグナルイベント", "xpack.siem.uiSettings.defaultAnomalyScoreDescription": "

機械学習ジョブの異常がこの値を超えると SIEM アプリに表示されます。

有効な値:0 ~ 100。

", "xpack.siem.uiSettings.defaultAnomalyScoreLabel": "デフォルトの異常しきい値", "xpack.siem.uiSettings.defaultIndexDescription": "

SIEM アプリがイベントを収集する Elasticsearch インデックスのコンマ区切りのリストです。

", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index bceded8ea3bbb..beaa51f87f874 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -168,7 +168,6 @@ "common.ui.flotCharts.thuLabel": "周四", "common.ui.flotCharts.tueLabel": "周二", "common.ui.flotCharts.wedLabel": "周三", - "common.ui.stackManagement.breadcrumb": "Stack Management", "common.ui.stateManagement.unableToParseUrlErrorMessage": "无法解析 URL", "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "无法完整还原 URL,确保使用共享功能。", "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。", @@ -565,7 +564,6 @@ "data.filter.filterBar.filterItemBadgeIconAriaLabel": "删除", "data.filter.filterBar.includeFilterButtonLabel": "包括结果", "data.filter.filterBar.indexPatternSelectPlaceholder": "选择索引模式", - "data.filter.filterBar.labelErrorMessage": "无法显示筛选", "data.filter.filterBar.labelErrorText": "错误", "data.filter.filterBar.moreFilterActionsMessage": "筛选:{innerText}。选择以获取更多筛选操作。", "data.filter.filterBar.negatedFilterPrefix": "非 ", @@ -2182,10 +2180,6 @@ "kbn.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "默认 WMS 属性", "kbn.advancedSettings.visualizeEnableLabsText": "允许用户创建、查看和编辑实验性可视化。如果禁用,\n 仅被视为生产就绪的可视化可供用户使用。", "kbn.advancedSettings.visualizeEnableLabsTitle": "启用实验性可视化", - "kbn.management.landing.header": "欢迎使用 Stack Management {version}", - "kbn.management.landing.subhead": "管理您的索引、索引模式、已保存对象、Kibana 设置等等。", - "kbn.management.landing.text": "应用的完整列表位于左侧菜单中。", - "kbn.managementTitle": "Stack Management", "kibana_legacy.bigUrlWarningNotificationMessage": "在{advancedSettingsLink}中启用“{storeInSessionStorageParam}”选项或简化屏幕视觉效果。", "kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高级设置", "kibana_legacy.bigUrlWarningNotificationTitle": "URL 过长,Kibana 可能无法工作", @@ -2227,9 +2221,11 @@ "kibana-react.tableListView.listing.table.editActionName": "编辑", "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "无法删除{entityName}", "management.breadcrumb": "Stack Management", - "management.displayName": "Stack Management", "management.nav.label": "管理", "management.nav.menu": "管理菜单", + "management.landing.header": "欢迎使用 Stack Management {version}", + "management.landing.subhead": "管理您的索引、索引模式、已保存对象、Kibana 设置等等。", + "management.landing.text": "应用的完整列表位于左侧菜单中。", "management.stackManagement.managementDescription": "您用于管理 Elastic Stack 的中心控制台。", "management.stackManagement.managementLabel": "Stack Management", "maps_legacy.baseMapsVisualization.childShouldImplementMethodErrorMessage": "子对象应实现此方法以响应数据更新", @@ -3986,16 +3982,16 @@ "xpack.advancedUiActions.customizePanelTimeRange.modal.updatePanelTimeRangeButtonTitle": "更新", "xpack.advancedUiActions.customizeTimeRange.modal.headerTitle": "定制面板时间范围", "xpack.advancedUiActions.customizeTimeRangeMenuItem.displayName": "定制时间范围", - "xpack.alerting.alertNavigationRegistry.get.missingNavigationError": "在“{consumer}”内针对告警类型“{alertType}”的导航未注册。", - "xpack.alerting.alertNavigationRegistry.register.duplicateDefaultError": "“{consumer}”内的默认导航已注册。", - "xpack.alerting.alertNavigationRegistry.register.duplicateNavigationError": "在“{consumer}”内针对告警类型“{alertType}”的导航已注册。", - "xpack.alerting.alertsClient.validateActions.invalidGroups": "无效操作组:{groups}", - "xpack.alerting.alertTypeRegistry.get.missingAlertTypeError": "未注册告警类型“{id}”。", - "xpack.alerting.alertTypeRegistry.register.duplicateAlertTypeError": "已注册告警类型“{id}”。", - "xpack.alerting.api.error.disabledApiKeys": "Alerting 依赖的 API 密钥似乎已禁用", - "xpack.alerting.appName": "Alerting", - "xpack.alerting.loadAlertType.missingAlertTypeError": "未注册告警类型“{id}”。", - "xpack.alerting.serverSideErrors.unavailableLicenseInformationErrorMessage": "Alerting 不可用 - 许可信息当前不可用。", + "xpack.alerts.alertNavigationRegistry.get.missingNavigationError": "在“{consumer}”内针对告警类型“{alertType}”的导航未注册。", + "xpack.alerts.alertNavigationRegistry.register.duplicateDefaultError": "“{consumer}”内的默认导航已注册。", + "xpack.alerts.alertNavigationRegistry.register.duplicateNavigationError": "在“{consumer}”内针对告警类型“{alertType}”的导航已注册。", + "xpack.alerts.alertsClient.validateActions.invalidGroups": "无效操作组:{groups}", + "xpack.alerts.alertTypeRegistry.get.missingAlertTypeError": "未注册告警类型“{id}”。", + "xpack.alerts.alertTypeRegistry.register.duplicateAlertTypeError": "已注册告警类型“{id}”。", + "xpack.alerts.api.error.disabledApiKeys": "Alerting 依赖的 API 密钥似乎已禁用", + "xpack.alerts.appName": "Alerting", + "xpack.alerts.loadAlertType.missingAlertTypeError": "未注册告警类型“{id}”。", + "xpack.alerts.serverSideErrors.unavailableLicenseInformationErrorMessage": "Alerting 不可用 - 许可信息当前不可用。", "xpack.alertingBuiltins.indexThreshold.actionGroupThresholdMetTitle": "阈值已达到", "xpack.alertingBuiltins.indexThreshold.actionVariableContextDateLabel": "告警超过阈值的日期。", "xpack.alertingBuiltins.indexThreshold.actionVariableContextGroupLabel": "超过阈值的组。", @@ -8120,16 +8116,13 @@ "xpack.ingestManager.agentConfigForm.nameRequiredErrorMessage": "“代理配置名称”必填", "xpack.ingestManager.agentConfigForm.namespaceFieldDescription": "将默认命名空间应用于使用此配置的数据源。数据源可以指定自己的命名空间。", "xpack.ingestManager.agentConfigForm.namespaceFieldLabel": "默认命名空间", - "xpack.ingestManager.agentConfigForm.namespaceUseDefaultsFieldLabel": "使用默认命名空间", "xpack.ingestManager.agentConfigForm.systemMonitoringFieldLabel": "可选", "xpack.ingestManager.agentConfigForm.systemMonitoringText": "收集系统指标", "xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText": "启用此选项可使用收集系统指标和信息的数据源启动您的配置。", "xpack.ingestManager.agentConfigList.actionsColumnTitle": "操作", - "xpack.ingestManager.agentConfigList.actionsMenuText": "打开", "xpack.ingestManager.agentConfigList.addButton": "创建代理配置", "xpack.ingestManager.agentConfigList.agentsColumnTitle": "代理", "xpack.ingestManager.agentConfigList.clearFiltersLinkText": "清除筛选", - "xpack.ingestManager.agentConfigList.createDatasourceActionText": "创建数据源", "xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle": "数据源", "xpack.ingestManager.agentConfigList.descriptionColumnTitle": "描述", "xpack.ingestManager.agentConfigList.loadingAgentConfigsMessage": "正在加载代理配置……", @@ -8141,7 +8134,6 @@ "xpack.ingestManager.agentConfigList.reloadAgentConfigsButtonText": "重新加载", "xpack.ingestManager.agentConfigList.revisionNumber": "修订 {revNumber}", "xpack.ingestManager.agentConfigList.updatedOnColumnTitle": "最后更新时间", - "xpack.ingestManager.agentConfigList.viewConfigActionText": "查看配置", "xpack.ingestManager.agentDetails.actionsButton": "操作", "xpack.ingestManager.agentDetails.agentConfigurationLabel": "代理配置", "xpack.ingestManager.agentDetails.agentDetailsTitle": "代理“{id}”", @@ -8202,7 +8194,6 @@ "xpack.ingestManager.agentHealth.onlineStatusText": "联机", "xpack.ingestManager.agentHealth.warningStatusText": "错误", "xpack.ingestManager.agentList.actionsColumnTitle": "操作", - "xpack.ingestManager.agentList.actionsMenuText": "打开", "xpack.ingestManager.agentList.addButton": "注册新代理", "xpack.ingestManager.agentList.clearFiltersLinkText": "清除筛选", "xpack.ingestManager.agentList.configColumnTitle": "配置", @@ -8270,7 +8261,6 @@ "xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "流计数", "xpack.ingestManager.configDetails.subTabs.datasourcesTabText": "数据源", "xpack.ingestManager.configDetails.subTabs.settingsTabText": "设置", - "xpack.ingestManager.configDetails.subTabs.yamlTabText": "YAML", "xpack.ingestManager.configDetails.summary.datasources": "数据源", "xpack.ingestManager.configDetails.summary.lastUpdated": "最后更新时间", "xpack.ingestManager.configDetails.summary.revision": "修订", @@ -13863,9 +13853,6 @@ "xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription": "无法安装 elastic 的预打包规则", "xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "已安装 elastic 的预打包规则", "xpack.siem.containers.detectionEngine.rules": "无法提取规则", - "xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription": "无法查询信号", - "xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription": "无法获取信号索引名称", - "xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription": "无法创建信号索引", "xpack.siem.containers.detectionEngine.tagFetchFailDescription": "无法提取标记", "xpack.siem.containers.errors.dataFetchFailureTitle": "数据提取失败", "xpack.siem.containers.errors.networkFailureTitle": "网络故障", @@ -13982,7 +13969,6 @@ "xpack.siem.detectionEngine.detectionsPageTitle": "检测", "xpack.siem.detectionEngine.dismissButton": "关闭", "xpack.siem.detectionEngine.dismissNoApiIntegrationKeyButton": "关闭", - "xpack.siem.detectionEngine.dismissNoWriteSignalButton": "关闭", "xpack.siem.detectionEngine.editRule.backToDescription": "返回到", "xpack.siem.detectionEngine.editRule.cancelTitle": "取消", "xpack.siem.detectionEngine.editRule.errorMsgDescription": "抱歉", @@ -13994,7 +13980,6 @@ "xpack.siem.detectionEngine.goToDocumentationButton": "查看文档", "xpack.siem.detectionEngine.headerPage.pageBadgeLabel": "公测版", "xpack.siem.detectionEngine.headerPage.pageBadgeTooltip": "“检测”仍为公测版。请通过在 Kibana 存储库中报告问题或错误,帮助我们改进产品。", - "xpack.siem.detectionEngine.lastSignalTitle": "上一信号", "xpack.siem.detectionEngine.mitreAttack.addTitle": "添加 MITRE ATT&CK\\u2122 威胁", "xpack.siem.detectionEngine.mitreAttack.tacticPlaceHolderDescription": "选择策略......", "xpack.siem.detectionEngine.mitreAttack.tacticsDescription": "策略", @@ -14284,8 +14269,6 @@ "xpack.siem.detectionEngine.noApiIntegrationKeyCallOutTitle": "需要 API 集成密钥", "xpack.siem.detectionEngine.noIndexMsgBody": "要使用检测引擎,具有所需集群和索引权限的用户必须首先访问此页面。若需要更多帮助,请联系您的管理员。", "xpack.siem.detectionEngine.noIndexTitle": "让我们来设置您的检测引擎", - "xpack.siem.detectionEngine.noWriteSignalsCallOutMsg": "您当前缺少所需的权限,无法更新信号。有关进一步帮助,请联系您的管理员。", - "xpack.siem.detectionEngine.noWriteSignalsCallOutTitle": "需要信号索引权限", "xpack.siem.detectionEngine.pageTitle": "检测引擎", "xpack.siem.detectionEngine.panelSubtitleShowing": "正在显示", "xpack.siem.detectionEngine.readOnlyCallOutMsg": "您当前缺少所需的权限,无法创建/编辑检测引擎规则。有关进一步帮助,请联系您的管理员。", @@ -14390,43 +14373,6 @@ "xpack.siem.detectionEngine.ruleStatus.statusDateDescription": "状态日期", "xpack.siem.detectionEngine.ruleStatus.statusDescription": "上次响应", "xpack.siem.detectionEngine.signalRuleAlert.actionGroups.default": "默认值", - "xpack.siem.detectionEngine.signals.actions.closeSignalTitle": "关闭信号", - "xpack.siem.detectionEngine.signals.actions.investigateInTimelineTitle": "在时间线中调查", - "xpack.siem.detectionEngine.signals.actions.openSignalTitle": "打开信号", - "xpack.siem.detectionEngine.signals.closedSignalsTitle": "已关闭信号", - "xpack.siem.detectionEngine.signals.documentTypeTitle": "信号", - "xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel": "所有其他", - "xpack.siem.detectionEngine.signals.histogram.headerTitle": "信号计数", - "xpack.siem.detectionEngine.signals.histogram.showingSignalsTitle": "正在显示:{modifier}{totalSignalsFormatted} 个{totalSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.destinationIpsDropDown": "排名靠前的目标 IP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventActionsDropDown": "排名靠前的事件操作", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventCategoriesDropDown": "排名靠前的事件类别", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.hostNamesDropDown": "排名靠前的主机名", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.riskScoresDropDown": "风险分数", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.rulesDropDown": "排名靠前的规则", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.ruleTypesDropDown": "排名靠前的规则类型", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.severitiesDropDown": "严重性", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.sourceIpsDropDown": "排名靠前的源 IP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.stackByLabel": "堆叠依据", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.usersDropDown": "排名靠前的用户", - "xpack.siem.detectionEngine.signals.histogram.topNLabel": "热门{fieldName}", - "xpack.siem.detectionEngine.signals.histogram.viewSignalsButtonLabel": "查看信号", - "xpack.siem.detectionEngine.signals.loadingSignalsTitle": "正在加载信号", - "xpack.siem.detectionEngine.signals.openSignalsTitle": "打开信号", - "xpack.siem.detectionEngine.signals.tableTitle": "信号", - "xpack.siem.detectionEngine.signals.totalCountOfSignalsTitle": "个信号匹配搜索条件", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.closeSelectedTitle": "关闭选定", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.openSelectedTitle": "打开选定", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInHostsTitle": "查看主机中所选", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInNetworkTitle": "查看网络中所选", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInTimelineTitle": "查看时间线中所选", - "xpack.siem.detectionEngine.signals.utilityBar.batchActionsTitle": "批量操作", - "xpack.siem.detectionEngine.signals.utilityBar.clearSelectionTitle": "清除选择", - "xpack.siem.detectionEngine.signals.utilityBar.selectAllSignalsTitle": "选择所有 {totalSignalsFormatted} 个{totalSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signals.utilityBar.selectedSignalsTitle": "已选择 {selectedSignalsFormatted} 个{selectedSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signals.utilityBar.showingSignalsTitle": "正在显示 {totalSignalsFormatted} 个{totalSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signalTitle": "检测到的信号", - "xpack.siem.detectionEngine.totalSignalTitle": "合计", "xpack.siem.detectionEngine.userUnauthenticatedMsgBody": "您没有所需的权限,无法查看检测引擎。若需要更多帮助,请联系您的管理员。", "xpack.siem.detectionEngine.userUnauthenticatedTitle": "需要检测引擎权限", "xpack.siem.dragAndDrop.addToTimeline": "添加到时间线调查", @@ -14466,11 +14412,6 @@ "xpack.siem.eventsViewer.eventsLabel": "事件", "xpack.siem.eventsViewer.footer.loadingEventsDataLabel": "正在加载事件", "xpack.siem.eventsViewer.showingLabel": "显示", - "xpack.siem.eventsViewer.signals.defaultHeaders.methodTitle": "方法", - "xpack.siem.eventsViewer.signals.defaultHeaders.riskScoreTitle": "风险分数", - "xpack.siem.eventsViewer.signals.defaultHeaders.ruleTitle": "规则", - "xpack.siem.eventsViewer.signals.defaultHeaders.severityTitle": "严重性", - "xpack.siem.eventsViewer.signals.defaultHeaders.versionTitle": "版本", "xpack.siem.eventsViewer.unit": "{totalCount, plural, =1 {个事件} other {个事件}}", "xpack.siem.featureCatalogue.description": "浏览安全指标和日志以了解事件和告警", "xpack.siem.featureCatalogue.title": "SIEM", @@ -14803,7 +14744,6 @@ "xpack.siem.overview.recentlyCreatedCasesButtonLabel": "最近创建的案例", "xpack.siem.overview.recentTimelinesSidebarTitle": "最近的时间线", "xpack.siem.overview.showTopTooltip": "显示热门{fieldName}", - "xpack.siem.overview.signalCountTitle": "信号计数", "xpack.siem.overview.startedText": "欢迎使用安全信息和事件管理 (SIEM)。首先,查看我们的 {docs} 或 {data}。有关即将推出的功能和教程,确保查看我们的{siemSolution}页。", "xpack.siem.overview.startedText.dataLinkText": "正在采集数据", "xpack.siem.overview.startedText.docsLinkText": "文档", @@ -14984,7 +14924,6 @@ "xpack.siem.topN.allEventsSelectLabel": "所有事件", "xpack.siem.topN.closeButtonLabel": "关闭", "xpack.siem.topN.rawEventsSelectLabel": "原始事件", - "xpack.siem.topN.signalEventsSelectLabel": "信号事件", "xpack.siem.uiSettings.defaultAnomalyScoreDescription": "

在显示异常之前要超过的默认异常分数阈值。

有效值:0 到 100。

", "xpack.siem.uiSettings.defaultAnomalyScoreLabel": "默认异常阈值", "xpack.siem.uiSettings.defaultIndexDescription": "

SIEM 应用要从其中搜索事件的 Elasticsearch 索引逗号分隔列表。

", diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index b07413ae73ee8..5a25f7b94050e 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -258,7 +258,7 @@ Each alert type should be defined as `AlertTypeModel` object with the these prop |requiresAppContext|Define if alert type is enabled for create and edit in the alerting management UI.| IMPORTANT: The current UI supports a single action group only. -Action groups are mapped from the server API result for [GET /api/alert/types: List alert types](https://github.com/elastic/kibana/tree/master/x-pack/legacy/plugins/alerting#get-apialerttypes-list-alert-types). +Action groups are mapped from the server API result for [GET /api/alerts/list_alert_types: List alert types](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerts#get-apialerttypes-list-alert-types). Server side alert type model: ``` export interface AlertType { @@ -296,7 +296,7 @@ triggers_actions_ui.alertTypeRegistry.register(getSomeNewAlertType()); ## Create and register new alert type UI example -Before registering a UI for a new Alert Type, you should first register the type on the server-side by following the Alerting guide: https://github.com/elastic/kibana/tree/master/x-pack/legacy/plugins/alerting#example +Before registering a UI for a new Alert Type, you should first register the type on the server-side by following the Alerting guide: https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerts#example Alert type UI is expected to be defined as `AlertTypeModel` object. @@ -1257,7 +1257,7 @@ Then this dependencies will be used to embed Actions form or register your own a const initialAlert = ({ name: 'test', params: {}, - consumer: 'alerting', + consumer: 'alerts', alertTypeId: '.index-threshold', schedule: { interval: '1m', diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index 11cbacee08afc..d8f5055368831 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.json +++ b/x-pack/plugins/triggers_actions_ui/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "server": false, "ui": true, - "optionalPlugins": ["alerting", "alertingBuiltins"], + "optionalPlugins": ["alerts", "alertingBuiltins"], "requiredPlugins": ["management", "charts", "data"], "configPath": ["xpack", "trigger_actions_ui"] } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index ebd9294ce1e6d..80f8e4a3a2c77 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { lazy } from 'react'; -import { Switch, Route, Redirect, HashRouter } from 'react-router-dom'; +import { Switch, Route, Redirect, Router } from 'react-router-dom'; import { ChromeStart, DocLinksStart, @@ -14,15 +14,16 @@ import { ApplicationStart, ChromeBreadcrumb, CoreStart, + ScopedHistory, } from 'kibana/public'; -import { BASE_PATH, Section, routeToAlertDetails } from './constants'; +import { Section, routeToAlertDetails } from './constants'; import { AppContextProvider, useAppDependencies } from './app_context'; import { hasShowAlertsCapability } from './lib/capabilities'; import { ActionTypeModel, AlertTypeModel } from '../types'; import { TypeRegistry } from './type_registry'; import { ChartsPluginStart } from '../../../../../src/plugins/charts/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; -import { PluginStartContract as AlertingStart } from '../../../alerting/public'; +import { PluginStartContract as AlertingStart } from '../../../alerts/public'; import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; const TriggersActionsUIHome = lazy(async () => import('./home')); @@ -34,7 +35,7 @@ export interface AppDeps { dataPlugin: DataPublicPluginStart; charts: ChartsPluginStart; chrome: ChromeStart; - alerting?: AlertingStart; + alerts?: AlertingStart; navigateToApp: CoreStart['application']['navigateToApp']; docLinks: DocLinksStart; toastNotifications: ToastsSetup; @@ -44,6 +45,7 @@ export interface AppDeps { capabilities: ApplicationStart['capabilities']; actionTypeRegistry: TypeRegistry; alertTypeRegistry: TypeRegistry; + history: ScopedHistory; } export const App = (appDeps: AppDeps) => { @@ -52,11 +54,11 @@ export const App = (appDeps: AppDeps) => { const sectionsRegex = sections.join('|'); return ( - + - +
); }; @@ -67,7 +69,7 @@ export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) = return ( {canShowAlerts && ( @@ -76,7 +78,7 @@ export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) = component={suspendedComponentWithProps(AlertDetailsRoute, 'xl')} /> )} - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx index 84cbc73ca92ca..244d431930f2e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx @@ -33,7 +33,7 @@ import { getThresholdAlertVisualizationData } from '../../../../common/lib/index import { AggregationType, Comparator } from '../../../../common/types'; import { AlertsContextValue } from '../../../context/alerts_context'; import { IndexThresholdAlertParams } from './types'; -import { parseDuration } from '../../../../../../alerting/common/parse_duration'; +import { parseDuration } from '../../../../../../alerts/common/parse_duration'; const customTheme = () => { return { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 265cfddab4c06..299d8272f5797 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -4,17 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -export { BASE_ALERT_API_PATH } from '../../../../alerting/common'; +export { BASE_ALERT_API_PATH } from '../../../../alerts/common'; export { BASE_ACTION_API_PATH } from '../../../../actions/common'; -export const BASE_PATH = '/management/insightsAndAlerting/triggersActions'; - export type Section = 'connectors' | 'alerts'; -export const routeToHome = `${BASE_PATH}`; -export const routeToConnectors = `${BASE_PATH}/connectors`; -export const routeToAlerts = `${BASE_PATH}/alerts`; -export const routeToAlertDetails = `${BASE_PATH}/alert/:alertId`; +export const routeToHome = `/`; +export const routeToConnectors = `/connectors`; +export const routeToAlerts = `/alerts`; +export const routeToAlertDetails = `/alert/:alertId`; export { TIME_UNITS } from './time_units'; export enum SORT_ORDERS { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index 8d35b60dc8e44..eeb8a77717333 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { BASE_PATH, Section, routeToConnectors, routeToAlerts } from './constants'; +import { Section, routeToConnectors, routeToAlerts } from './constants'; import { getCurrentBreadcrumb } from './lib/breadcrumb'; import { getCurrentDocTitle } from './lib/doc_title'; import { useAppDependencies } from './app_context'; @@ -76,7 +76,7 @@ export const TriggersActionsUIHome: React.FunctionComponent { - history.push(`${BASE_PATH}/${newSection}`); + history.push(`/${newSection}`); }; // Set breadcrumb and page title diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts index c35dd06385448..714dc5210e390 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts @@ -23,7 +23,7 @@ function prefixKeys(actionVariables: ActionVariable[], prefix: string): ActionVa } // this list should be the same as in: -// x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +// x-pack/plugins/alerts/server/task_runner/transform_action_params.ts function getAlwaysProvidedActionVariables(): ActionVariable[] { const result: ActionVariable[] = []; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index f384a78e2e080..94d9166b40909 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -53,7 +53,7 @@ describe('loadAlertTypes', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/types", + "/api/alerts/list_alert_types", ] `); }); @@ -80,7 +80,7 @@ describe('loadAlert', () => { http.get.mockResolvedValueOnce(resolvedValue); expect(await loadAlert({ http, alertId })).toEqual(resolvedValue); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}`); }); }); @@ -99,7 +99,7 @@ describe('loadAlertState', () => { http.get.mockResolvedValueOnce(resolvedValue); expect(await loadAlertState({ http, alertId })).toEqual(resolvedValue); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); test('should parse AlertInstances', async () => { @@ -136,7 +136,7 @@ describe('loadAlertState', () => { }, }, }); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); test('should handle empty response from api', async () => { @@ -144,7 +144,7 @@ describe('loadAlertState', () => { http.get.mockResolvedValueOnce(''); expect(await loadAlertState({ http, alertId })).toEqual({}); - expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`); + expect(http.get).toHaveBeenCalledWith(`/api/alerts/alert/${alertId}/state`); }); }); @@ -162,7 +162,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -192,7 +192,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -226,7 +226,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -260,7 +260,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -295,7 +295,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -330,7 +330,7 @@ describe('loadAlerts', () => { expect(result).toEqual(resolvedValue); expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/_find", + "/api/alerts/_find", Object { "query": Object { "default_search_operator": "AND", @@ -356,13 +356,13 @@ describe('deleteAlerts', () => { expect(http.delete.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1", + "/api/alerts/alert/1", ], Array [ - "/api/alert/2", + "/api/alerts/alert/2", ], Array [ - "/api/alert/3", + "/api/alerts/alert/3", ], ] `); @@ -373,7 +373,7 @@ describe('createAlert', () => { test('should call create alert API', async () => { const alertToCreate = { name: 'test', - consumer: 'alerting', + consumer: 'alerts', tags: ['foo'], enabled: true, alertTypeId: 'test', @@ -402,9 +402,9 @@ describe('createAlert', () => { expect(result).toEqual(resolvedValue); expect(http.post.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert", + "/api/alerts/alert", Object { - "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerting\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", + "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerts\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", }, ] `); @@ -415,7 +415,7 @@ describe('updateAlert', () => { test('should call alert update API', async () => { const alertToUpdate = { throttle: '1m', - consumer: 'alerting', + consumer: 'alerts', name: 'test', tags: ['foo'], schedule: { @@ -444,7 +444,7 @@ describe('updateAlert', () => { expect(result).toEqual(resolvedValue); expect(http.put.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "/api/alert/123", + "/api/alerts/alert/123", Object { "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[]}", }, @@ -460,7 +460,7 @@ describe('enableAlert', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_enable", + "/api/alerts/alert/1/_enable", ], ] `); @@ -474,7 +474,7 @@ describe('disableAlert', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_disable", + "/api/alerts/alert/1/_disable", ], ] `); @@ -488,7 +488,7 @@ describe('muteAlertInstance', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/alert_instance/123/_mute", + "/api/alerts/alert/1/alert_instance/123/_mute", ], ] `); @@ -502,7 +502,7 @@ describe('unmuteAlertInstance', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/alert_instance/123/_unmute", + "/api/alerts/alert/1/alert_instance/123/_unmute", ], ] `); @@ -516,7 +516,7 @@ describe('muteAlert', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_mute_all", + "/api/alerts/alert/1/_mute_all", ], ] `); @@ -530,7 +530,7 @@ describe('unmuteAlert', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_unmute_all", + "/api/alerts/alert/1/_unmute_all", ], ] `); @@ -545,13 +545,13 @@ describe('enableAlerts', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_enable", + "/api/alerts/alert/1/_enable", ], Array [ - "/api/alert/2/_enable", + "/api/alerts/alert/2/_enable", ], Array [ - "/api/alert/3/_enable", + "/api/alerts/alert/3/_enable", ], ] `); @@ -566,13 +566,13 @@ describe('disableAlerts', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_disable", + "/api/alerts/alert/1/_disable", ], Array [ - "/api/alert/2/_disable", + "/api/alerts/alert/2/_disable", ], Array [ - "/api/alert/3/_disable", + "/api/alerts/alert/3/_disable", ], ] `); @@ -587,13 +587,13 @@ describe('muteAlerts', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_mute_all", + "/api/alerts/alert/1/_mute_all", ], Array [ - "/api/alert/2/_mute_all", + "/api/alerts/alert/2/_mute_all", ], Array [ - "/api/alert/3/_mute_all", + "/api/alerts/alert/3/_mute_all", ], ] `); @@ -608,13 +608,13 @@ describe('unmuteAlerts', () => { expect(http.post.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/1/_unmute_all", + "/api/alerts/alert/1/_unmute_all", ], Array [ - "/api/alert/2/_unmute_all", + "/api/alerts/alert/2/_unmute_all", ], Array [ - "/api/alert/3/_unmute_all", + "/api/alerts/alert/3/_unmute_all", ], ] `); @@ -628,7 +628,7 @@ describe('health', () => { expect(http.get.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/alert/_health", + "/api/alerts/_health", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts index 2176f978822ca..35fdc3974a296 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts @@ -9,12 +9,12 @@ import * as t from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { pick } from 'lodash'; -import { alertStateSchema, AlertingFrameworkHealth } from '../../../../alerting/common'; +import { alertStateSchema, AlertingFrameworkHealth } from '../../../../alerts/common'; import { BASE_ALERT_API_PATH } from '../constants'; import { Alert, AlertType, AlertWithoutId, AlertTaskState } from '../../types'; export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/types`); + return await http.get(`${BASE_ALERT_API_PATH}/list_alert_types`); } export async function loadAlert({ @@ -24,7 +24,7 @@ export async function loadAlert({ http: HttpSetup; alertId: string; }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/${alertId}`); + return await http.get(`${BASE_ALERT_API_PATH}/alert/${alertId}`); } type EmptyHttpResponse = ''; @@ -36,7 +36,7 @@ export async function loadAlertState({ alertId: string; }): Promise { return await http - .get(`${BASE_ALERT_API_PATH}/${alertId}/state`) + .get(`${BASE_ALERT_API_PATH}/alert/${alertId}/state`) .then((state: AlertTaskState | EmptyHttpResponse) => (state ? state : {})) .then((state: AlertTaskState) => { return pipe( @@ -104,7 +104,7 @@ export async function deleteAlerts({ }): Promise<{ successes: string[]; errors: string[] }> { const successes: string[] = []; const errors: string[] = []; - await Promise.all(ids.map((id) => http.delete(`${BASE_ALERT_API_PATH}/${id}`))).then( + await Promise.all(ids.map((id) => http.delete(`${BASE_ALERT_API_PATH}/alert/${id}`))).then( function (fulfilled) { successes.push(...fulfilled); }, @@ -122,7 +122,7 @@ export async function createAlert({ http: HttpSetup; alert: Omit; }): Promise { - return await http.post(`${BASE_ALERT_API_PATH}`, { + return await http.post(`${BASE_ALERT_API_PATH}/alert`, { body: JSON.stringify(alert), }); } @@ -136,7 +136,7 @@ export async function updateAlert({ alert: Pick; id: string; }): Promise { - return await http.put(`${BASE_ALERT_API_PATH}/${id}`, { + return await http.put(`${BASE_ALERT_API_PATH}/alert/${id}`, { body: JSON.stringify( pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions']) ), @@ -144,7 +144,7 @@ export async function updateAlert({ } export async function enableAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/_enable`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_enable`); } export async function enableAlerts({ @@ -158,7 +158,7 @@ export async function enableAlerts({ } export async function disableAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/_disable`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_disable`); } export async function disableAlerts({ @@ -180,7 +180,7 @@ export async function muteAlertInstance({ instanceId: string; http: HttpSetup; }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/alert_instance/${instanceId}/_mute`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/alert_instance/${instanceId}/_mute`); } export async function unmuteAlertInstance({ @@ -192,11 +192,11 @@ export async function unmuteAlertInstance({ instanceId: string; http: HttpSetup; }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/alert_instance/${instanceId}/_unmute`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/alert_instance/${instanceId}/_unmute`); } export async function muteAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/_mute_all`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_mute_all`); } export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup }): Promise { @@ -204,7 +204,7 @@ export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup } export async function unmuteAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/${id}/_unmute_all`); + await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_unmute_all`); } export async function unmuteAlerts({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.test.ts index b75e014640d72..8ba909beff2a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.test.ts @@ -13,19 +13,19 @@ describe('getCurrentBreadcrumb', () => { text: i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', { defaultMessage: 'Connectors', }), - href: `#${routeToConnectors}`, + href: `${routeToConnectors}`, }); expect(getCurrentBreadcrumb('alerts')).toMatchObject({ text: i18n.translate('xpack.triggersActionsUI.alerts.breadcrumbTitle', { defaultMessage: 'Alerts', }), - href: `#${routeToAlerts}`, + href: `${routeToAlerts}`, }); expect(getCurrentBreadcrumb('home')).toMatchObject({ text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { defaultMessage: 'Alerts and Actions', }), - href: `#${routeToHome}`, + href: `${routeToHome}`, }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts index f833ae9eb39ac..3735942ff97af 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts @@ -15,21 +15,21 @@ export const getCurrentBreadcrumb = (type: string): { text: string; href: string text: i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', { defaultMessage: 'Connectors', }), - href: `#${routeToConnectors}`, + href: `${routeToConnectors}`, }; case 'alerts': return { text: i18n.translate('xpack.triggersActionsUI.alerts.breadcrumbTitle', { defaultMessage: 'Alerts', }), - href: `#${routeToAlerts}`, + href: `${routeToAlerts}`, }; default: return { text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { defaultMessage: 'Alerts and Actions', }), - href: `#${routeToHome}`, + href: `${routeToHome}`, }; } }; 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 df7d1e64c8e91..7ce952e9b3e0a 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 @@ -160,7 +160,7 @@ describe('action_form', () => { const initialAlert = ({ name: 'test', params: {}, - consumer: 'alerting', + consumer: 'alerts', alertTypeId: alertType.id, schedule: { interval: '1m', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx index 1da9abea40dba..f60199bc47f4b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx @@ -127,7 +127,7 @@ describe('connector_add_flyout', () => { const manageLink = callout.find('EuiButton'); expect(manageLink).toHaveLength(1); expect(manageLink.getElements()[0].props.href).toMatchInlineSnapshot( - `"/app/kibana#/management/stack/license_management/"` + `"/app/management/stack/license_management"` ); const subscriptionLink = callout.find('EuiButtonEmpty'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index dc54875f4a914..2dd1f83749372 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -32,7 +32,6 @@ import { createActionConnector } from '../../lib/action_connector_api'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; import { PLUGIN } from '../../constants/plugin'; -import { BASE_PATH as LICENSE_MANAGEMENT_BASE_PATH } from '../../../../../license_management/common/constants'; export interface ConnectorAddFlyoutProps { addFlyoutVisible: boolean; @@ -293,7 +292,7 @@ const UpgradeYourLicenseCallOut = ({ http }: { http: HttpSetup }) => ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 4a4fce5094f0d..a317c31a6e4b1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -4,16 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ import * as React from 'react'; +import { ScopedHistory } from 'kibana/public'; + import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ActionsConnectorsList } from './actions_connectors_list'; -import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../../src/core/public/mocks'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; import { AppContextProvider } from '../../../app_context'; import { chartPluginMock } from '../../../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; -import { alertingPluginMock } from '../../../../../../alerting/public/mocks'; +import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), @@ -66,6 +68,7 @@ describe('actions_connectors_list component empty', () => { 'actions:delete': true, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: {} as any, @@ -168,6 +171,7 @@ describe('actions_connectors_list component with items', () => { 'actions:delete': true, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: { get() { @@ -250,6 +254,7 @@ describe('actions_connectors_list component empty with show only capability', () 'actions:delete': false, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: { get() { @@ -335,6 +340,7 @@ describe('actions_connectors_list with show only capability', () => { 'actions:delete': false, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: { get() { @@ -432,6 +438,7 @@ describe('actions_connectors_list component with disabled items', () => { 'actions:delete': true, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: { get() { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx index e2d9c5cb7fffe..54d335aaba5aa 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.test.tsx @@ -13,7 +13,7 @@ import { ViewInApp } from './view_in_app'; import { useAppDependencies } from '../../../app_context'; jest.mock('../../../app_context', () => { - const alerting = { + const alerts = { getNavigation: jest.fn(async (id) => id === 'alert-with-nav' ? { path: '/alert' } : undefined ), @@ -23,7 +23,7 @@ jest.mock('../../../app_context', () => { useAppDependencies: jest.fn(() => ({ http: jest.fn(), navigateToApp, - alerting, + alerts, legacy: { capabilities: { get: jest.fn(() => ({})), @@ -41,7 +41,7 @@ describe('view in app', () => { describe('link to the app that created the alert', () => { it('is disabled when there is no navigation', async () => { const alert = mockAlert(); - const { alerting } = useAppDependencies(); + const { alerts } = useAppDependencies(); let component: ReactWrapper; await act(async () => { @@ -53,7 +53,7 @@ describe('view in app', () => { expect(component!.find('button').prop('disabled')).toBe(true); expect(component!.text()).toBe('View in app'); - expect(alerting!.getNavigation).toBeCalledWith(alert.id); + expect(alerts!.getNavigation).toBeCalledWith(alert.id); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx index f1f5d8323c22a..5b5de070a94e6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/view_in_app.tsx @@ -16,7 +16,7 @@ import { AlertNavigation, AlertStateNavigation, AlertUrlNavigation, -} from '../../../../../../alerting/common'; +} from '../../../../../../alerts/common'; import { Alert } from '../../../../types'; export interface ViewInAppProps { @@ -28,7 +28,7 @@ const NO_NAVIGATION = false; type AlertNavigationLoadingState = AlertNavigation | false | null; export const ViewInApp: React.FunctionComponent = ({ alert }) => { - const { navigateToApp, alerting: maybeAlerting } = useAppDependencies(); + const { navigateToApp, alerts: maybeAlerting } = useAppDependencies(); const [alertNavigation, setAlertNavigation] = useState(null); useEffect(() => { @@ -40,13 +40,14 @@ export const ViewInApp: React.FunctionComponent = ({ alert }) => * navigation isn't supported */ () => setAlertNavigation(NO_NAVIGATION), - (alerting) => - alerting + (alerts) => { + return alerts .getNavigation(alert.id) .then((nav) => (nav ? setAlertNavigation(nav) : setAlertNavigation(NO_NAVIGATION))) .catch(() => { setAlertNavigation(NO_NAVIGATION); - }) + }); + } ) ); }, [alert.id, maybeAlerting]); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index 56874f3d38b64..f6e8dc49ec275 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -120,11 +120,7 @@ describe('alert_add', () => { }, }} > - {}} - /> + {}} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index bb7e593170f8b..e408c7fcb8144 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -84,7 +84,7 @@ describe('alert_edit', () => { window: '1s', comparator: 'between', }, - consumer: 'alerting', + consumer: 'alerts', alertTypeId: 'my-alert-type', enabled: false, schedule: { interval: '1m' }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index ed36bc6c8d580..c9ce2848c5670 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -85,7 +85,7 @@ describe('alert_form', () => { const initialAlert = ({ name: 'test', params: {}, - consumer: 'alerting', + consumer: 'alerts', schedule: { interval: '1m', }, @@ -302,7 +302,7 @@ describe('alert_form', () => { name: 'test', alertTypeId: alertType.id, params: {}, - consumer: 'alerting', + consumer: 'alerts', schedule: { interval: '1m', }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 87e018ebe3376..874091b2bb7a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -30,7 +30,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { getDurationNumberInItsUnit, getDurationUnitValue, -} from '../../../../../alerting/common/parse_duration'; +} from '../../../../../alerts/common/parse_duration'; import { loadAlertTypes } from '../../lib/alert_api'; import { actionVariablesFromAlertType } from '../../lib/action_variables'; import { AlertReducerAction } from './alert_reducer'; @@ -168,7 +168,7 @@ export const AlertForm = ({ : null; const alertTypeRegistryList = - alert.consumer === 'alerting' + alert.consumer === 'alerts' ? alertTypeRegistry .list() .filter( @@ -179,6 +179,7 @@ export const AlertForm = ({ .filter( (alertTypeRegistryItem: AlertTypeModel) => alertTypesIndex && + alertTypesIndex[alertTypeRegistryItem.id] && alertTypesIndex[alertTypeRegistryItem.id].producer === alert.consumer ); const alertTypeNodes = alertTypeRegistryList.map(function (item, index) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts index bd320de144024..4e4d8e237aa2f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts @@ -11,7 +11,7 @@ describe('alert reducer', () => { beforeAll(() => { initialAlert = ({ params: {}, - consumer: 'alerting', + consumer: 'alerts', alertTypeId: null, schedule: { interval: '1m', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index cf1524094b41d..8166aa9bf6925 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import * as React from 'react'; +import { ScopedHistory } from 'kibana/public'; + import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../../src/core/public/mocks'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; @@ -15,7 +17,7 @@ import { ValidationResult } from '../../../../types'; import { AppContextProvider } from '../../../app_context'; import { chartPluginMock } from '../../../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; -import { alertingPluginMock } from '../../../../../../alerting/public/mocks'; +import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; jest.mock('../../../lib/action_connector_api', () => ({ loadActionTypes: jest.fn(), @@ -101,6 +103,7 @@ describe('alerts_list component empty', () => { 'alerting:delete': true, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: alertTypeRegistry as any, @@ -219,6 +222,7 @@ describe('alerts_list component with items', () => { 'alerting:delete': true, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: alertTypeRegistry as any, @@ -300,6 +304,7 @@ describe('alerts_list component empty with show only capability', () => { 'alerting:delete': false, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: { get() { @@ -414,6 +419,7 @@ describe('alerts_list with show only capability', () => { 'alerting:delete': false, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: alertTypeRegistry as any, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index bd4676cd83071..2929ce6defeaf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -439,7 +439,7 @@ export const AlertsList: React.FunctionComponent = () => { }} > diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index dcf120d37ef8b..db93e48ab3692 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -15,7 +15,7 @@ import { TypeRegistry } from './application/type_registry'; import { ManagementStart, ManagementSectionId } from '../../../../src/plugins/management/public'; import { boot } from './application/boot'; import { ChartsPluginStart } from '../../../../src/plugins/charts/public'; -import { PluginStartContract as AlertingStart } from '../../alerting/public'; +import { PluginStartContract as AlertingStart } from '../../alerts/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; export interface TriggersAndActionsUIPublicPluginSetup { @@ -32,7 +32,7 @@ interface PluginsStart { data: DataPublicPluginStart; charts: ChartsPluginStart; management: ManagementStart; - alerting?: AlertingStart; + alerts?: AlertingStart; navigateToApp: CoreStart['application']['navigateToApp']; } @@ -83,7 +83,7 @@ export class Plugin boot({ dataPlugin: plugins.data, charts: plugins.charts, - alerting: plugins.alerting, + alerts: plugins.alerts, element: params.element, toastNotifications: core.notifications.toasts, http: core.http, @@ -95,6 +95,7 @@ export class Plugin capabilities: core.application.capabilities, navigateToApp: core.application.navigateToApp, setBreadcrumbs: params.setBreadcrumbs, + history: params.history, actionTypeRegistry: this.actionTypeRegistry, alertTypeRegistry: this.alertTypeRegistry, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 11152c56c49ec..52179dd35767c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -5,7 +5,7 @@ */ import { HttpSetup, DocLinksStart } from 'kibana/public'; import { ComponentType } from 'react'; -import { ActionGroup } from '../../alerting/common'; +import { ActionGroup } from '../../alerts/common'; import { ActionType } from '../../actions/common'; import { TypeRegistry } from './application/type_registry'; import { @@ -14,7 +14,7 @@ import { AlertTaskState, RawAlertInstance, AlertingFrameworkHealth, -} from '../../../plugins/alerting/common'; +} from '../../alerts/common'; export { Alert, AlertAction, AlertTaskState, RawAlertInstance, AlertingFrameworkHealth }; export { ActionType }; diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/__snapshots__/stringify_kueries.test.ts.snap b/x-pack/plugins/uptime/common/lib/__tests__/__snapshots__/stringify_kueries.test.ts.snap similarity index 100% rename from x-pack/plugins/uptime/public/lib/helper/__tests__/__snapshots__/stringify_kueries.test.ts.snap rename to x-pack/plugins/uptime/common/lib/__tests__/__snapshots__/stringify_kueries.test.ts.snap diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/combine_filters_and_user_search.test.ts b/x-pack/plugins/uptime/common/lib/__tests__/combine_filters_and_user_search.test.ts similarity index 100% rename from x-pack/plugins/uptime/public/lib/helper/__tests__/combine_filters_and_user_search.test.ts rename to x-pack/plugins/uptime/common/lib/__tests__/combine_filters_and_user_search.test.ts diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_kueries.test.ts b/x-pack/plugins/uptime/common/lib/__tests__/stringify_kueries.test.ts similarity index 69% rename from x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_kueries.test.ts rename to x-pack/plugins/uptime/common/lib/__tests__/stringify_kueries.test.ts index db1a846c295a0..4c32b19765427 100644 --- a/x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_kueries.test.ts +++ b/x-pack/plugins/uptime/common/lib/__tests__/stringify_kueries.test.ts @@ -59,4 +59,32 @@ describe('stringifyKueries', () => { kueries.set('monitor.id', ['https://elastic.co', 'https://example.com']); expect(stringifyKueries(kueries)).toMatchSnapshot(); }); + + it('handles precending empty array', () => { + kueries = new Map( + Object.entries({ + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], + tags: [], + 'url.port': [], + }) + ); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"(observer.geo.name:us-east or observer.geo.name:apj or observer.geo.name:sydney or observer.geo.name:us-west)"` + ); + }); + + it('handles skipped empty arrays', () => { + kueries = new Map( + Object.entries({ + tags: [], + 'monitor.type': ['http'], + 'url.port': [], + 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], + }) + ); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"monitor.type:http and (observer.geo.name:us-east or observer.geo.name:apj or observer.geo.name:sydney or observer.geo.name:us-west)"` + ); + }); }); diff --git a/x-pack/plugins/uptime/public/lib/helper/combine_filters_and_user_search.ts b/x-pack/plugins/uptime/common/lib/combine_filters_and_user_search.ts similarity index 100% rename from x-pack/plugins/uptime/public/lib/helper/combine_filters_and_user_search.ts rename to x-pack/plugins/uptime/common/lib/combine_filters_and_user_search.ts diff --git a/x-pack/plugins/rollup/public/crud_app/constants/paths.js b/x-pack/plugins/uptime/common/lib/index.ts similarity index 73% rename from x-pack/plugins/rollup/public/crud_app/constants/paths.js rename to x-pack/plugins/uptime/common/lib/index.ts index 44829f38e79cd..2daec0adf87e4 100644 --- a/x-pack/plugins/rollup/public/crud_app/constants/paths.js +++ b/x-pack/plugins/uptime/common/lib/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CRUD_APP_BASE_PATH = '/management/data/rollup_jobs'; +export * from './combine_filters_and_user_search'; +export * from './stringify_kueries'; diff --git a/x-pack/plugins/uptime/public/lib/helper/stringify_kueries.ts b/x-pack/plugins/uptime/common/lib/stringify_kueries.ts similarity index 93% rename from x-pack/plugins/uptime/public/lib/helper/stringify_kueries.ts rename to x-pack/plugins/uptime/common/lib/stringify_kueries.ts index d34ee9665de33..490fd1661f782 100644 --- a/x-pack/plugins/uptime/public/lib/helper/stringify_kueries.ts +++ b/x-pack/plugins/uptime/common/lib/stringify_kueries.ts @@ -35,6 +35,10 @@ export const stringifyKueries = (kueries: Map>): .reduce((prev, cur, index, array) => { if (array.length === 1 || index === 0) { return cur; + } else if (cur === '') { + return prev; + } else if (prev === '' && !!cur) { + return cur; } return `${prev} and ${cur}`; }, ''); diff --git a/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts b/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts index 909669bb5d3eb..74d5337256601 100644 --- a/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts +++ b/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts @@ -6,7 +6,30 @@ import * as t from 'io-ts'; -export const StatusCheckExecutorParamsType = t.intersection([ +export const StatusCheckFiltersType = t.type({ + 'monitor.type': t.array(t.string), + 'observer.geo.name': t.array(t.string), + tags: t.array(t.string), + 'url.port': t.array(t.string), +}); + +export type StatusCheckFilters = t.TypeOf; + +export const AtomicStatusCheckParamsType = t.intersection([ + t.type({ + numTimes: t.number, + timerangeCount: t.number, + timerangeUnit: t.string, + }), + t.partial({ + search: t.string, + filters: StatusCheckFiltersType, + }), +]); + +export type AtomicStatusCheckParams = t.TypeOf; + +export const StatusCheckParamsType = t.intersection([ t.partial({ filters: t.string, }), @@ -20,4 +43,4 @@ export const StatusCheckExecutorParamsType = t.intersection([ }), ]); -export type StatusCheckExecutorParams = t.TypeOf; +export type StatusCheckParams = t.TypeOf; diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts index d8dc7fc89d94b..ab539b38c3e41 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts @@ -36,9 +36,9 @@ export const MonitorType = t.intersection([ check_group: t.string, ip: t.string, name: t.string, - timespan: t.partial({ + timespan: t.type({ gte: t.string, - lte: t.string, + lt: t.string, }), }), ]); @@ -55,13 +55,13 @@ export const PingType = t.intersection([ agent: t.intersection([ t.type({ ephemeral_id: t.string, - hostname: t.string, id: t.string, type: t.string, version: t.string, }), t.partial({ name: t.string, + hostname: t.string, }), ]), container: t.partial({ diff --git a/x-pack/plugins/uptime/common/translations.ts b/x-pack/plugins/uptime/common/translations.ts index 678fe7cb1f984..81f46df86f02e 100644 --- a/x-pack/plugins/uptime/common/translations.ts +++ b/x-pack/plugins/uptime/common/translations.ts @@ -6,9 +6,13 @@ import { i18n } from '@kbn/i18n'; -export const VALUE_MUST_BE_GREATER_THEN_ZEO = i18n.translate( +export const VALUE_MUST_BE_GREATER_THAN_ZERO = i18n.translate( 'xpack.uptime.settings.invalid.error', { defaultMessage: 'Value must be greater than 0.', } ); + +export const VALUE_MUST_BE_AN_INTEGER = i18n.translate('xpack.uptime.settings.invalid.nanError', { + defaultMessage: 'Value must be an integer.', +}); diff --git a/x-pack/plugins/uptime/kibana.json b/x-pack/plugins/uptime/kibana.json index ce8b64ce07254..5fbd6129fd18f 100644 --- a/x-pack/plugins/uptime/kibana.json +++ b/x-pack/plugins/uptime/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "optionalPlugins": ["capabilities", "data", "home"], "requiredPlugins": [ - "alerting", + "alerts", "embeddable", "features", "licensing", diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap index 5ae90a1613575..2ba4eda82a391 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap @@ -28,7 +28,7 @@ Array [

@@ -64,7 +64,7 @@ exports[`ShowLicenseInfo shallow renders without errors 1`] = `

Start free 14-day trial diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap index df9de8c2ad03d..3a056e6621f58 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap @@ -160,7 +160,7 @@ exports[`ML Flyout component shows license info if no ml available 1`] = `

diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx index 33fbbb117a11a..e37ec4cc4715d 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx @@ -22,7 +22,7 @@ export const ShowLicenseInfo = () => {

{labels.START_TRAIL_DESC}

{labels.START_TRAIL} diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx index 62f92fe8a5142..e2e44124ec659 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { DataPublicPluginSetup } from 'src/plugins/data/public'; import * as labels from './translations'; @@ -35,10 +35,6 @@ export const AlertMonitorStatusComponent: React.FC = (p const [newFilters, setNewFilters] = useState([]); - useEffect(() => { - setAlertParams('filters', filters); - }, [filters, setAlertParams]); - return ( <> diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx index 77e0c98c0260d..973a3e1d477b6 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; import { DataPublicPluginSetup } from 'src/plugins/data/public'; -import { selectMonitorStatusAlert } from '../../../../state/selectors'; +import { selectMonitorStatusAlert, searchTextSelector } from '../../../../state/selectors'; import { AlertMonitorStatusComponent } from '../index'; interface Props { @@ -29,6 +29,12 @@ export const AlertMonitorStatus: React.FC = ({ timerange, }) => { const { filters, locations } = useSelector(selectMonitorStatusAlert); + const searchText = useSelector(searchTextSelector); + + useEffect(() => { + setAlertParams('search', searchText); + }, [setAlertParams, searchText]); + return ( = ({ updatedFieldValues.values ); - useEffect(() => { - if (updatedFieldValues.fieldName === 'observer.geo.name') { - setAlertParams('locations', updatedFieldValues.values); - } - }, [setAlertParams, updatedFieldValues]); + const [filters, setFilters] = useState({ + 'observer.geo.name': selectedLocations, + 'url.port': selectedPorts, + tags: selectedTags, + 'monitor.type': selectedSchemes, + }); useEffect(() => { - setAlertParams('locations', []); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + setAlertParams('filters', filters); + }, [filters, setAlertParams]); const onFilterFieldChange = (fieldName: string, values: string[]) => { + setFilters({ + ...filters, + [fieldName]: values, + }); setUpdatedFieldValues({ fieldName, values }); }; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx index aabc6fd6e6623..e3893845862fb 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx @@ -51,7 +51,8 @@ export const TimeExpressionSelect: React.FC = ({ setAlertParams }) => { useEffect(() => { const timerangeUnit = timerangeUnitOptions.find(({ checked }) => checked === 'on')?.key ?? 'm'; - setAlertParams('timerange', { from: `now-${numUnits}${timerangeUnit}`, to: 'now' }); + setAlertParams('timerangeUnit', timerangeUnit); + setAlertParams('timerangeCount', numUnits); }, [numUnits, timerangeUnitOptions, setAlertParams]); return ( diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx index 34c5a48ad55f0..18514bd92d7a0 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx @@ -61,7 +61,7 @@ export const ToggleAlertFlyoutButtonComponent: React.FC = ({ ({ suggestions: [], @@ -56,6 +57,10 @@ export function KueryBar({ const [getUrlParams, updateUrlParams] = useUrlParams(); const { search: kuery } = getUrlParams(); + useEffect(() => { + updateSearchText(kuery); + }, [kuery, updateSearchText]); + const indexPatternMissing = loading && !indexPattern; async function onChange(inputValue: string, selectionStart: number) { @@ -63,6 +68,8 @@ export function KueryBar({ return; } + updateSearchText(inputValue); + setIsLoadingSuggestions(true); setState({ ...state, suggestions: [] }); diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx index cf6d137e8692b..29e24a83cfa15 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx @@ -10,7 +10,7 @@ import { CursorDirection, SortOrder, } from '../../../../../common/runtime_types'; -import { MonitorListComponent } from '../monitor_list'; +import { MonitorListComponent, noItemsMessage } from '../monitor_list'; import { renderWithRouter, shallowWithRouter } from '../../../../lib'; import * as redux from 'react-redux'; @@ -288,4 +288,24 @@ describe('MonitorList component', () => { expect(component).toMatchSnapshot(); }); }); + + describe('noItemsMessage', () => { + it('returns loading message while loading', () => { + expect(noItemsMessage(true)).toEqual(`Loading...`); + }); + + it('returns loading message when filters are defined and loading', () => { + expect(noItemsMessage(true, 'filters')).toEqual(`Loading...`); + }); + + it('returns no monitors selected when filters are defined and not loading', () => { + expect(noItemsMessage(false, 'filters')).toEqual( + `No monitors found for selected filter criteria` + ); + }); + + it('returns no data message when no filters and not loading', () => { + expect(noItemsMessage(false)).toEqual(`No uptime monitors found`); + }); + }); }); diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx index c1095f6ec71f0..1526838460957 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx @@ -43,6 +43,11 @@ const TruncatedEuiLink = styled(EuiLink)` text-overflow: ellipsis; `; +export const noItemsMessage = (loading: boolean, filters?: string) => { + if (loading) return labels.LOADING; + return !!filters ? labels.NO_MONITOR_ITEM_SELECTED : labels.NO_DATA_MESSAGE; +}; + export const MonitorListComponent: React.FC = ({ filters, monitorList: { list, error, loading }, @@ -164,7 +169,7 @@ export const MonitorListComponent: React.FC = ({ itemId="monitor_id" itemIdToExpandedRowMap={getExpandedRowMap()} items={items} - noItemsMessage={!!filters ? labels.NO_MONITOR_ITEM_SELECTED : labels.NO_DATA_MESSAGE} + noItemsMessage={noItemsMessage(loading, filters)} columns={columns} /> diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts b/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts index 90dc854cc6904..e70eef1d91161 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts @@ -52,6 +52,11 @@ export const NO_MONITOR_ITEM_SELECTED = i18n.translate( } ); +export const LOADING = i18n.translate('xpack.uptime.monitorList.loading', { + defaultMessage: 'Loading...', + description: 'Shown when the monitor list is waiting for a server response', +}); + export const NO_DATA_MESSAGE = i18n.translate('xpack.uptime.monitorList.noItemMessage', { defaultMessage: 'No uptime monitors found', description: 'This message is shown if the monitors table is rendered but has no items.', diff --git a/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx b/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx index 8e2348a60ddab..2b587d23247e2 100644 --- a/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx +++ b/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { CertificateExpirationForm } from '../certificate_form'; -import { shallowWithRouter } from '../../../lib'; +import { shallowWithRouter, mountWithRouter } from '../../../lib'; describe('CertificateForm', () => { it('shallow renders expected elements for valid props', () => { @@ -26,4 +26,110 @@ describe('CertificateForm', () => { ) ).toMatchSnapshot(); }); + + it('submits number values for certs settings fields', () => { + const onChangeMock = jest.fn(); + const wrapper = mountWithRouter( + + ); + + const inputs = wrapper.find('input'); + + expect(inputs).toHaveLength(2); + + // expiration threshold input + inputs.at(0).simulate('change', { + target: { + value: '23', + }, + }); + + // age threshold input + inputs.at(1).simulate('change', { + target: { + value: '56', + }, + }); + + expect(onChangeMock).toHaveBeenCalledTimes(2); + + expect(onChangeMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "certExpirationThreshold": 23, + }, + ] + `); + + expect(onChangeMock.mock.calls[1]).toMatchInlineSnapshot(` + Array [ + Object { + "certAgeThreshold": 56, + }, + ] + `); + }); + + it('submits undefined for NaN values', () => { + const onChangeMock = jest.fn(); + const wrapper = mountWithRouter( + + ); + + const inputs = wrapper.find('input'); + + expect(inputs).toHaveLength(2); + + // expiration threshold input + inputs.at(0).simulate('change', { + target: { + value: 'A', + }, + }); + + // age threshold input + inputs.at(1).simulate('change', { + target: { + value: 'g', + }, + }); + + expect(onChangeMock).toHaveBeenCalledTimes(2); + + expect(onChangeMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "certExpirationThreshold": undefined, + }, + ] + `); + + expect(onChangeMock.mock.calls[1]).toMatchInlineSnapshot(` + Array [ + Object { + "certAgeThreshold": undefined, + }, + ] + `); + }); }); diff --git a/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx b/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx index 38c0d0663c937..dcc3b97575983 100644 --- a/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx +++ b/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx @@ -10,7 +10,7 @@ import { EuiDescribedFormGroup, EuiFormRow, EuiCode, - EuiFieldNumber, + EuiFieldText, EuiText, EuiTitle, EuiSpacer, @@ -80,17 +80,17 @@ export const CertificateExpirationForm: React.FC = ({ > - onChange({ - certExpirationThreshold: Number(e.target.value), + certExpirationThreshold: Number(e.target.value) || undefined, }) } /> @@ -128,17 +128,17 @@ export const CertificateExpirationForm: React.FC = ({ > - + onChange={({ target: { value } }) => onChange({ - certAgeThreshold: Number(value), + certAgeThreshold: Number(value) || undefined, }) } /> diff --git a/x-pack/plugins/uptime/public/hooks/index.ts b/x-pack/plugins/uptime/public/hooks/index.ts index b92d2d4cf7df5..14264710f7a0d 100644 --- a/x-pack/plugins/uptime/public/hooks/index.ts +++ b/x-pack/plugins/uptime/public/hooks/index.ts @@ -9,3 +9,4 @@ export * from './use_url_params'; export * from './use_telemetry'; export * from './update_kuery_string'; export * from './use_cert_status'; +export * from './use_search_text'; diff --git a/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts b/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts index 492d2bab5bb80..8a9e134e98bfd 100644 --- a/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts +++ b/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineFiltersAndUserSearch, stringifyKueries } from '../lib/helper'; import { esKuery, IIndexPattern } from '../../../../../src/plugins/data/public'; +import { combineFiltersAndUserSearch, stringifyKueries } from '../../common/lib'; const getKueryString = (urlFilters: string): string => { let kueryString = ''; diff --git a/x-pack/plugins/uptime/public/hooks/use_search_text.ts b/x-pack/plugins/uptime/public/hooks/use_search_text.ts new file mode 100644 index 0000000000000..8226c2365b767 --- /dev/null +++ b/x-pack/plugins/uptime/public/hooks/use_search_text.ts @@ -0,0 +1,22 @@ +/* + * 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 { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { setSearchTextAction } from '../state/actions'; +import { searchTextSelector } from '../state/selectors'; + +export const useSearchText = () => { + const dispatch = useDispatch(); + const searchText = useSelector(searchTextSelector); + + const updateSearchText = useCallback( + (nextSearchText: string) => dispatch(setSearchTextAction(nextSearchText)), + [dispatch] + ); + + return { searchText, updateSearchText }; +}; diff --git a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts index b06a7cc93f628..098a999b0d89c 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts @@ -12,12 +12,9 @@ describe('monitor status alert type', () => { beforeEach(() => { params = { - locations: [], numTimes: 5, - timerange: { - from: 'now-15m', - to: 'now', - }, + timerangeCount: 15, + timerangeUnit: 'm', }; }); @@ -27,9 +24,9 @@ describe('monitor status alert type', () => { "errors": Object { "typeCheckFailure": "Provided parameters do not conform to the expected type.", "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/locations: Array", - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/numTimes: number", - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/timerangeCount: number", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/timerangeUnit: string", ], }, } @@ -37,88 +34,21 @@ describe('monitor status alert type', () => { }); describe('timerange', () => { - it('is undefined', () => { - delete params.timerange; - expect(validate(params)).toMatchInlineSnapshot(` - Object { - "errors": Object { - "typeCheckFailure": "Provided parameters do not conform to the expected type.", - "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }", - ], - }, - } - `); - }); - - it('is missing `from` or `to` value', () => { - expect( - validate({ - ...params, - timerange: {}, - }) - ).toMatchInlineSnapshot(` - Object { - "errors": Object { - "typeCheckFailure": "Provided parameters do not conform to the expected type.", - "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }/from: string", - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }/to: string", - ], - }, - } - `); - }); - - it('is invalid timespan', () => { - expect( - validate({ - ...params, - timerange: { - from: 'now', - to: 'now-15m', - }, - }) - ).toMatchInlineSnapshot(` - Object { - "errors": Object { - "invalidTimeRange": "Time range start cannot exceed time range end", - }, - } - `); - }); - - it('has unparseable `from` value', () => { - expect( - validate({ - ...params, - timerange: { - from: 'cannot parse this to a date', - to: 'now', - }, - }) - ).toMatchInlineSnapshot(` + it('has invalid timerangeCount value', () => { + expect(validate({ ...params, timerangeCount: 0 })).toMatchInlineSnapshot(` Object { "errors": Object { - "timeRangeStartValueNaN": "Specified time range \`from\` is an invalid value", + "invalidTimeRangeValue": "Time range value must be greater than 0", }, } `); }); - it('has unparseable `to` value', () => { - expect( - validate({ - ...params, - timerange: { - from: 'now-15m', - to: 'cannot parse this to a date', - }, - }) - ).toMatchInlineSnapshot(` + it('has NaN timerangeCount value', () => { + expect(validate({ ...params, timerangeCount: NaN })).toMatchInlineSnapshot(` Object { "errors": Object { - "timeRangeEndValueNaN": "Specified time range \`to\` is an invalid value", + "timeRangeStartValueNaN": "Specified time range value must be a number", }, } `); @@ -133,7 +63,7 @@ describe('monitor status alert type', () => { "errors": Object { "typeCheckFailure": "Provided parameters do not conform to the expected type.", "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/numTimes: number", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number", ], }, } @@ -146,7 +76,7 @@ describe('monitor status alert type', () => { "errors": Object { "typeCheckFailure": "Provided parameters do not conform to the expected type.", "typeCheckParsingMessage": Array [ - "Invalid value \\"this isn't a number\\" supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/numTimes: number", + "Invalid value \\"this isn't a number\\" supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number", ], }, } diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index 08fc044bee201..a39317f8db1ed 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -4,51 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PathReporter } from 'io-ts/lib/PathReporter'; import React from 'react'; -import DateMath from '@elastic/datemath'; import { isRight } from 'fp-ts/lib/Either'; +import { PathReporter } from 'io-ts/lib/PathReporter'; import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; import { AlertTypeInitializer } from '.'; -import { StatusCheckExecutorParamsType } from '../../../common/runtime_types'; +import { AtomicStatusCheckParamsType } from '../../../common/runtime_types'; import { MonitorStatusTitle } from './monitor_status_title'; import { CLIENT_ALERT_TYPES } from '../../../common/constants'; import { MonitorStatusTranslations } from './translations'; -export const validate = (alertParams: any) => { +export const validate = (alertParams: unknown) => { const errors: Record = {}; - const decoded = StatusCheckExecutorParamsType.decode(alertParams); + const decoded = AtomicStatusCheckParamsType.decode(alertParams); - /* - * When the UI initially loads, this validate function is called with an - * empty set of params, we don't want to type check against that. - */ if (!isRight(decoded)) { errors.typeCheckFailure = 'Provided parameters do not conform to the expected type.'; errors.typeCheckParsingMessage = PathReporter.report(decoded); - } - - if (isRight(decoded)) { - const { numTimes, timerange } = decoded.right; - const { from, to } = timerange; - const fromAbs = DateMath.parse(from)?.valueOf(); - const toAbs = DateMath.parse(to)?.valueOf(); - if (!fromAbs || isNaN(fromAbs)) { - errors.timeRangeStartValueNaN = 'Specified time range `from` is an invalid value'; - } - if (!toAbs || isNaN(toAbs)) { - errors.timeRangeEndValueNaN = 'Specified time range `to` is an invalid value'; - } - - // the default values for this test will pass, we only want to specify an error - // in the case that `from` is more recent than `to` - if ((fromAbs ?? 0) > (toAbs ?? 1)) { - errors.invalidTimeRange = 'Time range start cannot exceed time range end'; - } - + } else { + const { numTimes, timerangeCount } = decoded.right; if (numTimes < 1) { errors.invalidNumTimes = 'Number of alert check down times must be an integer greater than 0'; } + if (isNaN(timerangeCount)) { + errors.timeRangeStartValueNaN = 'Specified time range value must be a number'; + } + if (timerangeCount <= 0) { + errors.invalidTimeRangeValue = 'Time range value must be greater than 0'; + } } return { errors }; diff --git a/x-pack/plugins/uptime/public/lib/helper/index.ts b/x-pack/plugins/uptime/public/lib/helper/index.ts index e2aa4a2b3d429..cf49328141b83 100644 --- a/x-pack/plugins/uptime/public/lib/helper/index.ts +++ b/x-pack/plugins/uptime/public/lib/helper/index.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { combineFiltersAndUserSearch } from './combine_filters_and_user_search'; export { convertMicrosecondsToMilliseconds } from './convert_measurements'; export * from './observability_integration'; export { getChartDateLabel } from './charts'; export { seriesHasDownValues } from './series_has_down_values'; -export { stringifyKueries } from './stringify_kueries'; export { UptimeUrlParams, getSupportedUrlParams } from './url_params'; diff --git a/x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx b/x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx new file mode 100644 index 0000000000000..601feee8f8159 --- /dev/null +++ b/x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx @@ -0,0 +1,35 @@ +/* + * 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 { isValidCertVal } from '../settings'; + +describe('settings', () => { + describe('isValidCertVal', () => { + it('handles NaN values', () => { + expect(isValidCertVal(NaN)).toMatchInlineSnapshot(`"Must be a number."`); + }); + + it('handles undefined', () => { + expect(isValidCertVal(undefined)).toMatchInlineSnapshot(`"Must be a number."`); + }); + + it('handles non-integer numbers', () => { + expect(isValidCertVal(23.5)).toMatchInlineSnapshot(`"Value must be an integer."`); + }); + + it('handles values less than 0', () => { + expect(isValidCertVal(-1)).toMatchInlineSnapshot(`"Value must be greater than 0."`); + }); + + it('handles 0', () => { + expect(isValidCertVal(0)).toMatchInlineSnapshot(`"Value must be greater than 0."`); + }); + + it('allows valid integer numbers', () => { + expect(isValidCertVal(67)).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/uptime/public/pages/settings.tsx b/x-pack/plugins/uptime/public/pages/settings.tsx index 807da83821261..6285c0cc0f1da 100644 --- a/x-pack/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.tsx @@ -31,7 +31,10 @@ import { OnFieldChangeType, } from '../components/settings/certificate_form'; import * as Translations from './translations'; -import { VALUE_MUST_BE_GREATER_THEN_ZEO } from '../../common/translations'; +import { + VALUE_MUST_BE_GREATER_THAN_ZERO, + VALUE_MUST_BE_AN_INTEGER, +} from '../../common/translations'; interface SettingsPageFieldErrors { heartbeatIndices: string | ''; @@ -47,12 +50,15 @@ export interface SettingsFormProps { isDisabled: boolean; } -const isValidCertVal = (val: string | number) => { - if (val === '') { - return Translations.BLANK_STR; +export const isValidCertVal = (val?: number): string | undefined => { + if (val === undefined || isNaN(val)) { + return Translations.settings.mustBeNumber; + } + if (val <= 0) { + return VALUE_MUST_BE_GREATER_THAN_ZERO; } - if (val === 0) { - return VALUE_MUST_BE_GREATER_THEN_ZEO; + if (val % 1) { + return VALUE_MUST_BE_AN_INTEGER; } }; diff --git a/x-pack/plugins/uptime/public/pages/translations.ts b/x-pack/plugins/uptime/public/pages/translations.ts index 8ed5503235884..dae4760edc469 100644 --- a/x-pack/plugins/uptime/public/pages/translations.ts +++ b/x-pack/plugins/uptime/public/pages/translations.ts @@ -35,6 +35,9 @@ export const settings = { returnToOverviewLinkLabel: i18n.translate('xpack.uptime.settings.returnToOverviewLinkLabel', { defaultMessage: 'Return to overview', }), + mustBeNumber: i18n.translate('xpack.uptime.settings.blankNumberField.error', { + defaultMessage: 'Must be a number.', + }), }; export const BLANK_STR = i18n.translate('xpack.uptime.settings.blank.error', { diff --git a/x-pack/plugins/uptime/public/state/actions/ui.ts b/x-pack/plugins/uptime/public/state/actions/ui.ts index 0d21e177d3e40..04ad6c2fa0bf3 100644 --- a/x-pack/plugins/uptime/public/state/actions/ui.ts +++ b/x-pack/plugins/uptime/public/state/actions/ui.ts @@ -20,6 +20,8 @@ export const setBasePath = createAction('SET BASE PATH'); export const setEsKueryString = createAction('SET ES KUERY STRING'); +export const setSearchTextAction = createAction('SET SEARCH'); + export const toggleIntegrationsPopover = createAction( 'TOGGLE INTEGRATION POPOVER STATE' ); diff --git a/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap b/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap index f8faf78fbc504..c11b146101d35 100644 --- a/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap +++ b/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap @@ -9,6 +9,7 @@ Object { "id": "popover-2", "open": true, }, + "searchText": "", } `; @@ -18,5 +19,6 @@ Object { "basePath": "yyz", "esKuery": "", "integrationsPopoverOpen": null, + "searchText": "", } `; diff --git a/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts b/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts index 94bc626088c84..3b8447ec2d713 100644 --- a/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts +++ b/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts @@ -18,6 +18,7 @@ describe('ui reducer', () => { basePath: 'abc', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, action ) @@ -36,6 +37,7 @@ describe('ui reducer', () => { basePath: '', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, action ) @@ -51,6 +53,7 @@ describe('ui reducer', () => { basePath: '', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, action ) @@ -60,6 +63,7 @@ describe('ui reducer', () => { "basePath": "", "esKuery": "", "integrationsPopoverOpen": null, + "searchText": "", } `); }); diff --git a/x-pack/plugins/uptime/public/state/reducers/ui.ts b/x-pack/plugins/uptime/public/state/reducers/ui.ts index 9e7bc2ad02723..3cf4ae9c0bbf2 100644 --- a/x-pack/plugins/uptime/public/state/reducers/ui.ts +++ b/x-pack/plugins/uptime/public/state/reducers/ui.ts @@ -13,6 +13,7 @@ import { UiPayload, setAlertFlyoutType, setAlertFlyoutVisible, + setSearchTextAction, } from '../actions'; export interface UiState { @@ -20,6 +21,7 @@ export interface UiState { alertFlyoutType?: string; basePath: string; esKuery: string; + searchText: string; integrationsPopoverOpen: PopoverState | null; } @@ -27,6 +29,7 @@ const initialState: UiState = { alertFlyoutVisible: false, basePath: '', esKuery: '', + searchText: '', integrationsPopoverOpen: null, }; @@ -56,6 +59,11 @@ export const uiReducer = handleActions( ...state, alertFlyoutType: action.payload, }), + + [String(setSearchTextAction)]: (state, action: Action) => ({ + ...state, + searchText: action.payload, + }), }, initialState ); diff --git a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts index d8121e29d0cae..2eb0f1e8cb0ee 100644 --- a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts +++ b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -44,6 +44,7 @@ describe('state selectors', () => { basePath: 'yyz', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, monitorStatus: { status: null, diff --git a/x-pack/plugins/uptime/public/state/selectors/index.ts b/x-pack/plugins/uptime/public/state/selectors/index.ts index ce295faaf5763..b088c346ad811 100644 --- a/x-pack/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/plugins/uptime/public/state/selectors/index.ts @@ -84,3 +84,5 @@ export const monitorListSelector = ({ monitorList }: AppState) => monitorList; export const overviewFiltersSelector = ({ overviewFilters }: AppState) => overviewFilters; export const esKuerySelector = ({ ui: { esKuery } }: AppState) => esKuery; + +export const searchTextSelector = ({ ui: { searchText } }: AppState) => searchText; diff --git a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index f4d1c72770494..8d26838811262 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -23,7 +23,7 @@ export type APICaller = ( export type UMElasticsearchQueryFn = ( params: { callES: APICaller; dynamicSettings: DynamicSettings } & P -) => Promise | R; +) => Promise; export type UMSavedObjectsQueryFn = ( client: SavedObjectsClientContract | ISavedObjectsRepository, @@ -36,7 +36,7 @@ export interface UptimeCoreSetup { export interface UptimeCorePlugins { features: PluginSetupContract; - alerting: any; + alerts: any; elasticsearch: any; usageCollection: UsageCollectionSetup; } diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 73d104c1d21ae..6cd836525c077 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -6,17 +6,19 @@ import { contextMessage, - uniqueMonitorIds, - statusCheckAlertFactory, fullListByIdAndLocation, + genFilterString, + hasFilters, + statusCheckAlertFactory, + uniqueMonitorIds, } from '../status_check'; import { GetMonitorStatusResult } from '../../requests'; -import { AlertType } from '../../../../../alerting/server'; +import { AlertType } from '../../../../../alerts/server'; import { IRouter } from 'kibana/server'; import { UMServerLibs } from '../../lib'; import { UptimeCoreSetup } from '../../adapters'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; -import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks'; +import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; /** * The alert takes some dependencies as parameters; these are things like @@ -39,7 +41,7 @@ const bootstrapDependencies = (customRequests?: any) => { * This function aims to provide an easy way to give mock props that will * reduce boilerplate for tests. * @param params the params received at alert creation time - * @param services the core services provided by kibana/alerting platforms + * @param services the core services provided by kibana/alerts platforms * @param state the state the alert maintains */ const mockOptions = ( @@ -310,9 +312,12 @@ describe('status check alert', () => { expect(Object.keys(alert.validate?.params?.props ?? {})).toMatchInlineSnapshot(` Array [ "filters", + "locations", "numTimes", + "search", + "timerangeCount", + "timerangeUnit", "timerange", - "locations", ] `); }); @@ -332,6 +337,205 @@ describe('status check alert', () => { }); }); + describe('hasFilters', () => { + it('returns false for undefined filters', () => { + expect(hasFilters()).toBe(false); + }); + + it('returns false for empty filters', () => { + expect( + hasFilters({ + 'monitor.type': [], + 'observer.geo.name': [], + tags: [], + 'url.port': [], + }) + ).toBe(false); + }); + + it('returns true for an object with a filter', () => { + expect( + hasFilters({ + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'us-west'], + tags: [], + 'url.port': [], + }) + ).toBe(true); + }); + }); + + describe('genFilterString', () => { + const mockGetIndexPattern = jest.fn(); + mockGetIndexPattern.mockReturnValue(undefined); + + it('returns `undefined` for no filters or search', async () => { + expect(await genFilterString(mockGetIndexPattern)).toBeUndefined(); + }); + + it('creates a filter string for filters only', async () => { + const res = await genFilterString(mockGetIndexPattern, { + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'us-west'], + tags: [], + 'url.port': [], + }); + expect(res).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-east", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-west", + }, + }, + ], + }, + }, + ], + }, + } + `); + }); + + it('creates a filter string for search only', async () => { + expect(await genFilterString(mockGetIndexPattern, undefined, 'monitor.id: "kibana-dev"')) + .toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.id": "kibana-dev", + }, + }, + ], + }, + } + `); + }); + + it('creates a filter string for filters and string', async () => { + const res = await genFilterString( + mockGetIndexPattern, + { + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], + tags: [], + 'url.port': [], + }, + 'monitor.id: "kibana-dev"' + ); + expect(res).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-east", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "apj", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "sydney", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-west", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.id": "kibana-dev", + }, + }, + ], + }, + }, + ], + }, + } + `); + }); + }); + describe('uniqueMonitorIds', () => { let items: GetMonitorStatusResult[]; beforeEach(() => { diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 17479bb451b18..cd42082b42c84 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -8,14 +8,22 @@ import { schema } from '@kbn/config-schema'; import { isRight } from 'fp-ts/lib/Either'; import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; import { i18n } from '@kbn/i18n'; -import { AlertExecutorOptions } from '../../../../alerting/server'; +import { AlertExecutorOptions } from '../../../../alerts/server'; import { UptimeAlertTypeFactory } from './types'; import { GetMonitorStatusResult } from '../requests'; -import { StatusCheckExecutorParamsType } from '../../../common/runtime_types'; +import { esKuery, IIndexPattern } from '../../../../../../src/plugins/data/server'; +import { JsonObject } from '../../../../../../src/plugins/kibana_utils/common'; +import { + StatusCheckParamsType, + StatusCheckParams, + StatusCheckFilters, + AtomicStatusCheckParamsType, +} from '../../../common/runtime_types'; import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants'; import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; import { commonStateTranslations } from './translations'; +import { stringifyKueries, combineFiltersAndUserSearch } from '../../../common/lib'; const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS; @@ -124,6 +132,44 @@ export const fullListByIdAndLocation = ( // we might want to make this a parameter in the future const DEFAULT_MAX_MESSAGE_ROWS = 3; +export const hasFilters = (filters?: StatusCheckFilters) => { + if (!filters) return false; + for (const list of Object.values(filters)) { + if (list.length > 0) { + return true; + } + } + return false; +}; + +export const genFilterString = async ( + getIndexPattern: () => Promise, + filters?: StatusCheckFilters, + search?: string +): Promise => { + const filtersExist = hasFilters(filters); + if (!filtersExist && !search) return undefined; + + let filterString: string | undefined; + if (filtersExist) { + filterString = stringifyKueries(new Map(Object.entries(filters ?? {}))); + } + + let combinedString: string | undefined; + if (filterString && search) { + combinedString = combineFiltersAndUserSearch(filterString, search); + } else if (filterString) { + combinedString = filterString; + } else if (search) { + combinedString = search; + } + + return esKuery.toElasticsearchQuery( + esKuery.fromKueryExpression(combinedString ?? ''), + await getIndexPattern() + ); +}; + export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ id: 'xpack.uptime.alerts.monitorStatus', name: i18n.translate('xpack.uptime.alerts.monitorStatus', { @@ -131,13 +177,28 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = }), validate: { params: schema.object({ - filters: schema.maybe(schema.string()), + filters: schema.maybe( + schema.oneOf([ + schema.object({ + 'monitor.type': schema.maybe(schema.arrayOf(schema.string())), + 'observer.geo.name': schema.maybe(schema.arrayOf(schema.string())), + tags: schema.maybe(schema.arrayOf(schema.string())), + 'url.port': schema.maybe(schema.arrayOf(schema.string())), + }), + schema.string(), + ]) + ), + locations: schema.maybe(schema.arrayOf(schema.string())), numTimes: schema.number(), - timerange: schema.object({ - from: schema.string(), - to: schema.string(), - }), - locations: schema.arrayOf(schema.string()), + search: schema.maybe(schema.string()), + timerangeCount: schema.maybe(schema.number()), + timerangeUnit: schema.maybe(schema.string()), + timerange: schema.maybe( + schema.object({ + from: schema.string(), + to: schema.string(), + }) + ), }), }, defaultActionGroupId: MONITOR_STATUS.id, @@ -174,18 +235,41 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = producer: 'uptime', async executor(options: AlertExecutorOptions) { const { params: rawParams } = options; - const decoded = StatusCheckExecutorParamsType.decode(rawParams); - if (!isRight(decoded)) { + const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( + options.services.savedObjectsClient + ); + const atomicDecoded = AtomicStatusCheckParamsType.decode(rawParams); + const decoded = StatusCheckParamsType.decode(rawParams); + let params: StatusCheckParams; + if (isRight(atomicDecoded)) { + const { filters, search, numTimes, timerangeCount, timerangeUnit } = atomicDecoded.right; + const timerange = { from: `now-${String(timerangeCount) + timerangeUnit}`, to: 'now' }; + const filterString = JSON.stringify( + await genFilterString( + () => + libs.requests.getIndexPattern({ + callES: options.services.callCluster, + dynamicSettings, + }), + filters, + search + ) + ); + params = { + timerange, + numTimes, + locations: [], + filters: filterString, + }; + } else if (isRight(decoded)) { + params = decoded.right; + } else { ThrowReporter.report(decoded); return { error: 'Alert param types do not conform to required shape.', }; } - const params = decoded.right; - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( - options.services.savedObjectsClient - ); /* This is called `monitorsByLocation` but it's really * monitors by location by status. The query we run to generate this * filters on the status field, so effectively there should be one and only one diff --git a/x-pack/plugins/uptime/server/lib/alerts/types.ts b/x-pack/plugins/uptime/server/lib/alerts/types.ts index bc1e82224f7b0..a321cc124ac22 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/types.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertType } from '../../../../alerting/server'; +import { AlertType } from '../../../../alerts/server'; import { UptimeCoreSetup } from '../adapters'; import { UMServerLibs } from '../lib'; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts b/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts index 7902d9a5c8536..7b08752f12249 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts @@ -8,7 +8,7 @@ import { APICaller, CallAPIOptions } from 'src/core/server'; import { UMElasticsearchQueryFn } from '../adapters'; import { IndexPatternsFetcher, IIndexPattern } from '../../../../../../src/plugins/data/server'; -export const getUptimeIndexPattern: UMElasticsearchQueryFn<{}, {}> = async ({ +export const getUptimeIndexPattern: UMElasticsearchQueryFn<{}, IIndexPattern | undefined> = async ({ callES, dynamicSettings, }) => { diff --git a/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts b/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts index 9f153e186420d..53b1fe881cd92 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts @@ -15,6 +15,7 @@ import { SortOrder, } from '../../../../common/runtime_types'; import { MonitorEnricher } from './fetch_page'; +import { getHistogramInterval } from '../../helper/get_histogram_interval'; export const enrichMonitorGroups: MonitorEnricher = async ( queryContext: QueryContext, @@ -317,11 +318,13 @@ const getHistogramForMonitors = async ( }, aggs: { histogram: { - auto_date_histogram: { + date_histogram: { field: '@timestamp', // 12 seems to be a good size for performance given // long monitor lists of up to 100 on the overview page - buckets: 12, + fixed_interval: + getHistogramInterval(queryContext.dateRangeStart, queryContext.dateRangeEnd, 12) + + 'ms', missing: 0, }, aggs: { diff --git a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts index 367db924cf1c6..85fc2c3ef9771 100644 --- a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts +++ b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts @@ -33,13 +33,14 @@ import { } from '.'; import { GetMonitorStatesResult } from './get_monitor_states'; import { GetSnapshotCountParams } from './get_snapshot_counts'; +import { IIndexPattern } from '../../../../../../src/plugins/data/server'; type ESQ = UMElasticsearchQueryFn; export interface UptimeRequests { getCerts: ESQ; getFilterBar: ESQ; - getIndexPattern: ESQ<{}, {}>; + getIndexPattern: ESQ<{}, IIndexPattern | undefined>; getLatestMonitor: ESQ; getMonitorDurationChart: ESQ; getMonitorDetails: ESQ; diff --git a/x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts b/x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts new file mode 100644 index 0000000000000..32e6385bc9fa2 --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts @@ -0,0 +1,77 @@ +/* + * 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 { validateCertsValues } from '../dynamic_settings'; + +describe('dynamic settings', () => { + describe('validateCertValues', () => { + it(`doesn't allow age threshold values less than 0`, () => { + expect( + validateCertsValues({ + certAgeThreshold: -1, + certExpirationThreshold: 2, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certAgeThreshold": "Value must be greater than 0.", + } + `); + }); + + it(`doesn't allow non-integer age threshold values`, () => { + expect( + validateCertsValues({ + certAgeThreshold: 10.2, + certExpirationThreshold: 2, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certAgeThreshold": "Value must be an integer.", + } + `); + }); + + it(`doesn't allow expiration threshold values less than 0`, () => { + expect( + validateCertsValues({ + certAgeThreshold: 2, + certExpirationThreshold: -1, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certExpirationThreshold": "Value must be greater than 0.", + } + `); + }); + + it(`doesn't allow non-integer expiration threshold values`, () => { + expect( + validateCertsValues({ + certAgeThreshold: 2, + certExpirationThreshold: 1.23, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certExpirationThreshold": "Value must be an integer.", + } + `); + }); + + it('allows valid values', () => { + expect( + validateCertsValues({ + certAgeThreshold: 2, + certExpirationThreshold: 13, + heartbeatIndices: 'foo', + }) + ).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts index c7d532d932aa6..6dba36ec0613d 100644 --- a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts +++ b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts @@ -11,7 +11,10 @@ import { UMServerLibs } from '../lib/lib'; import { DynamicSettings, DynamicSettingsType } from '../../common/runtime_types'; import { UMRestApiRouteFactory } from '.'; import { savedObjectsAdapter } from '../lib/saved_objects'; -import { VALUE_MUST_BE_GREATER_THEN_ZEO } from '../../common/translations'; +import { + VALUE_MUST_BE_GREATER_THAN_ZERO, + VALUE_MUST_BE_AN_INTEGER, +} from '../../common/translations'; export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', @@ -24,13 +27,19 @@ export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSer }, }); -const validateCertsValues = (settings: DynamicSettings) => { +export const validateCertsValues = ( + settings: DynamicSettings +): Record | undefined => { const errors: any = {}; if (settings.certAgeThreshold <= 0) { - errors.certAgeThreshold = VALUE_MUST_BE_GREATER_THEN_ZEO; + errors.certAgeThreshold = VALUE_MUST_BE_GREATER_THAN_ZERO; + } else if (settings.certAgeThreshold % 1) { + errors.certAgeThreshold = VALUE_MUST_BE_AN_INTEGER; } if (settings.certExpirationThreshold <= 0) { - errors.certExpirationThreshold = VALUE_MUST_BE_GREATER_THEN_ZEO; + errors.certExpirationThreshold = VALUE_MUST_BE_GREATER_THAN_ZERO; + } else if (settings.certExpirationThreshold % 1) { + errors.certExpirationThreshold = VALUE_MUST_BE_AN_INTEGER; } if (errors.certAgeThreshold || errors.certExpirationThreshold) { return errors; diff --git a/x-pack/plugins/uptime/server/uptime_server.ts b/x-pack/plugins/uptime/server/uptime_server.ts index 180067c0abde2..fb90dfe2be6c5 100644 --- a/x-pack/plugins/uptime/server/uptime_server.ts +++ b/x-pack/plugins/uptime/server/uptime_server.ts @@ -19,6 +19,6 @@ export const initUptimeServer = ( ); uptimeAlertTypeFactories.forEach((alertTypeFactory) => - plugins.alerting.registerType(alertTypeFactory(server, libs)) + plugins.alerts.registerType(alertTypeFactory(server, libs)) ); }; diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx b/x-pack/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx index 27aa3ba93684e..142504ee163b7 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx @@ -7,11 +7,14 @@ import React from 'react'; import { of } from 'rxjs'; import { ComponentType } from 'enzyme'; +import { LocationDescriptorObject } from 'history'; +import { ScopedHistory } from 'src/core/public'; import { docLinksServiceMock, uiSettingsServiceMock, notificationServiceMock, httpServiceMock, + scopedHistoryMock, } from '../../../../../../src/core/public/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AppContextProvider } from '../../../public/application/app_context'; @@ -27,6 +30,12 @@ class MockTimeBuckets { }; } } + +const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; +history.createHref = (location: LocationDescriptorObject) => { + return `${location.pathname}${location.search ? '?' + location.search : ''}`; +}; + export const mockContextValue = { licenseStatus$: of({ valid: true }), docLinks: docLinksServiceMock.createStartContract(), @@ -39,6 +48,8 @@ export const mockContextValue = { } as any, // For our test harness, we don't use this mocked out http service http: httpServiceMock.createSetupContract(), + history, + getUrlForApp: jest.fn(), }; export const withAppContext = (Component: ComponentType) => (props: any) => { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts index 0436ed1d6bb06..e436971edbb69 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts @@ -131,7 +131,7 @@ describe('', () => { expect(findTestSubject(idColumn, `watchIdColumn-${watch1.id}`).length).toBe(1); expect(findTestSubject(idColumn, `watchIdColumn-${watch1.id}`).props().href).toEqual( - `#/management/insightsAndAlerting/watcher/watches/watch/${watch1.id}/status` + `/watches/watch/${watch1.id}/status` ); }); diff --git a/x-pack/plugins/watcher/public/application/app.tsx b/x-pack/plugins/watcher/public/application/app.tsx index 8a6d2746237e9..555440f9fbbec 100644 --- a/x-pack/plugins/watcher/public/application/app.tsx +++ b/x-pack/plugins/watcher/public/application/app.tsx @@ -6,29 +6,30 @@ import React, { useEffect, useState } from 'react'; import { Observable } from 'rxjs'; -import { DocLinksStart, HttpSetup, ToastsSetup, IUiSettingsClient } from 'kibana/public'; - import { - HashRouter, - Switch, - Route, - Redirect, - withRouter, - RouteComponentProps, -} from 'react-router-dom'; + DocLinksStart, + HttpSetup, + ToastsSetup, + IUiSettingsClient, + ApplicationStart, +} from 'kibana/public'; + +import { Router, Switch, Route, Redirect, withRouter, RouteComponentProps } from 'react-router-dom'; import { EuiCallOut, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { RegisterManagementAppArgs } from '../../../../../src/plugins/management/public'; +import { + RegisterManagementAppArgs, + ManagementAppMountParams, +} from '../../../../../src/plugins/management/public'; import { LicenseStatus } from '../../common/types/license_status'; import { WatchStatus } from './sections/watch_status/components/watch_status'; import { WatchEdit } from './sections/watch_edit/components/watch_edit'; import { WatchList } from './sections/watch_list/components/watch_list'; import { registerRouter } from './lib/navigation'; -import { BASE_PATH } from './constants'; import { AppContextProvider } from './app_context'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; @@ -46,6 +47,8 @@ export interface AppDeps { createTimeBuckets: () => any; licenseStatus$: Observable; setBreadcrumbs: Parameters[0]['setBreadcrumbs']; + history: ManagementAppMountParams['history']; + getUrlForApp: ApplicationStart['getUrlForApp']; } export const App = (deps: AppDeps) => { @@ -69,7 +72,7 @@ export const App = (deps: AppDeps) => { iconType="help" > {message}{' '} - + { ); } return ( - + - + ); }; // Export this so we can test it with a different router. export const AppWithoutRouter = () => ( - - - - - + + + + + + ); diff --git a/x-pack/plugins/watcher/public/application/lib/breadcrumbs.ts b/x-pack/plugins/watcher/public/application/lib/breadcrumbs.ts index db0e34fc57683..6fd47d4d487ae 100644 --- a/x-pack/plugins/watcher/public/application/lib/breadcrumbs.ts +++ b/x-pack/plugins/watcher/public/application/lib/breadcrumbs.ts @@ -15,7 +15,7 @@ export const listBreadcrumb: Breadcrumb = { text: i18n.translate('xpack.watcher.breadcrumb.listLabel', { defaultMessage: 'Watcher', }), - href: '#/management/insightsAndAlerting/watcher/watches/', + href: '/watches', }; export const createBreadcrumb: Breadcrumb = { diff --git a/x-pack/plugins/watcher/public/application/lib/navigation.ts b/x-pack/plugins/watcher/public/application/lib/navigation.ts index 87fa95ce53410..1ace9d36f2e3a 100644 --- a/x-pack/plugins/watcher/public/application/lib/navigation.ts +++ b/x-pack/plugins/watcher/public/application/lib/navigation.ts @@ -4,20 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BASE_PATH } from '../constants'; let router: any; export const registerRouter = (aRouter: any) => { router = aRouter; }; export const goToWatchList = () => { - router.history.push({ pathname: `${BASE_PATH}watches` }); + router.history.push({ pathname: `/watches` }); }; export const goToCreateThresholdAlert = () => { - router.history.push({ pathname: `${BASE_PATH}watches/new-watch/threshold` }); + router.history.push({ pathname: `/watches/new-watch/threshold` }); }; export const goToCreateAdvancedWatch = () => { - router.history.push({ pathname: `${BASE_PATH}watches/new-watch/json` }); + router.history.push({ pathname: `/watches/new-watch/json` }); }; diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx index 156a6f2eda890..b3fc8af81c94a 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx @@ -18,9 +18,13 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { WatchContext } from '../../watch_context'; +import { useAppContext } from '../../../../app_context'; + +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; export const MonitoringWatchEdit = ({ pageTitle }: { pageTitle: string }) => { const { watch } = useContext(WatchContext); + const { history } = useAppContext(); const systemWatchTitle = ( { values={{ watchName: watch.name, watchStatusLink: ( - + { // hooks const { setBreadcrumbs, + history, links: { watcherGettingStartedUrl }, } = useAppContext(); const [selection, setSelection] = useState([]); @@ -242,7 +245,7 @@ export const WatchList = () => { return ( {id} @@ -326,7 +329,7 @@ export const WatchList = () => { )} iconType="pencil" color="primary" - href={`#/management/insightsAndAlerting/watcher/watches/watch/${watch.id}/edit`} + {...reactRouterNavigate(history, `/watches/watch/${watch.id}/edit`)} data-test-subj="editWatchButton" /> diff --git a/x-pack/plugins/watcher/public/plugin.ts b/x-pack/plugins/watcher/public/plugin.ts index 6496c742fcb40..80f8503af67ce 100644 --- a/x-pack/plugins/watcher/public/plugin.ts +++ b/x-pack/plugins/watcher/public/plugin.ts @@ -38,9 +38,9 @@ export class WatcherUIPlugin implements Plugin { { defaultMessage: 'Watcher' } ), order: 3, - mount: async ({ element, setBreadcrumbs }) => { + mount: async ({ element, setBreadcrumbs, history }) => { const [core] = await getStartServices(); - const { i18n: i18nDep, docLinks, savedObjects } = core; + const { i18n: i18nDep, docLinks, savedObjects, application } = core; const { boot } = await import('./application/boot'); const { TimeBuckets } = await import('./legacy'); @@ -58,6 +58,8 @@ export class WatcherUIPlugin implements Plugin { savedObjects: savedObjects.client, I18nContext: i18nDep.Context, createTimeBuckets: () => new TimeBuckets(uiSettings, data), + history, + getUrlForApp: application.getUrlForApp, }); }, }); @@ -76,7 +78,7 @@ export class WatcherUIPlugin implements Plugin { defaultMessage: 'Detect changes in your data by creating, managing, and monitoring alerts.', }), icon: 'watchesApp', - path: '/app/kibana#/management/insightsAndAlerting/watcher/watches', + path: '/app/management/insightsAndAlerting/watcher/watches', showOnHomePage: false, }; diff --git a/x-pack/test/accessibility/apps/search_profiler.ts b/x-pack/test/accessibility/apps/search_profiler.ts index 8a13940695f9e..138231d3cf025 100644 --- a/x-pack/test/accessibility/apps/search_profiler.ts +++ b/x-pack/test/accessibility/apps/search_profiler.ts @@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const a11y = getService('a11y'); const flyout = getService('flyout'); - describe('Accessibility Search Profiler Editor', () => { + // FLAKY: https://github.com/elastic/kibana/issues/67821 + describe.skip('Accessibility Search Profiler Editor', () => { before(async () => { await PageObjects.common.navigateToApp('searchProfiler'); await a11y.testAppSnapshot(); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json index 98c57db16c914..fc42e3199095d 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json @@ -3,7 +3,7 @@ "version": "1.0.0", "kibanaVersion": "kibana", "configPath": ["xpack"], - "requiredPlugins": ["taskManager", "features", "actions", "alerting"], + "requiredPlugins": ["taskManager", "features", "actions", "alerts"], "optionalPlugins": ["spaces"], "server": true, "ui": false diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts index bfabbb8169391..8e3d6b6909a14 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts @@ -8,11 +8,11 @@ import { CoreSetup } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { times } from 'lodash'; import { FixtureStartDeps, FixtureSetupDeps } from './plugin'; -import { AlertType, AlertExecutorOptions } from '../../../../../../../plugins/alerting/server'; +import { AlertType, AlertExecutorOptions } from '../../../../../../../plugins/alerts/server'; export function defineAlertTypes( core: CoreSetup, - { alerting }: Pick + { alerts }: Pick ) { const clusterClient = core.elasticsearch.legacy.client; const alwaysFiringAlertType: AlertType = { @@ -286,13 +286,13 @@ export function defineAlertTypes( }, async executor(opts: AlertExecutorOptions) {}, }; - alerting.registerType(alwaysFiringAlertType); - alerting.registerType(cumulativeFiringAlertType); - alerting.registerType(neverFiringAlertType); - alerting.registerType(failingAlertType); - alerting.registerType(validationAlertType); - alerting.registerType(authorizationAlertType); - alerting.registerType(noopAlertType); - alerting.registerType(onlyContextVariablesAlertType); - alerting.registerType(onlyStateVariablesAlertType); + alerts.registerType(alwaysFiringAlertType); + alerts.registerType(cumulativeFiringAlertType); + alerts.registerType(neverFiringAlertType); + alerts.registerType(failingAlertType); + alerts.registerType(validationAlertType); + alerts.registerType(authorizationAlertType); + alerts.registerType(noopAlertType); + alerts.registerType(onlyContextVariablesAlertType); + alerts.registerType(onlyStateVariablesAlertType); } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts index af8dd0282c578..47563f8a5f078 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts @@ -6,7 +6,7 @@ import { Plugin, CoreSetup } from 'kibana/server'; import { PluginSetupContract as ActionsPluginSetup } from '../../../../../../../plugins/actions/server/plugin'; -import { PluginSetupContract as AlertingPluginSetup } from '../../../../../../../plugins/alerting/server/plugin'; +import { PluginSetupContract as AlertingPluginSetup } from '../../../../../../../plugins/alerts/server/plugin'; import { EncryptedSavedObjectsPluginStart } from '../../../../../../../plugins/encrypted_saved_objects/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../../plugins/features/server'; import { defineAlertTypes } from './alert_types'; @@ -16,7 +16,7 @@ import { defineRoutes } from './routes'; export interface FixtureSetupDeps { features: FeaturesPluginSetup; actions: ActionsPluginSetup; - alerting: AlertingPluginSetup; + alerts: AlertingPluginSetup; } export interface FixtureStartDeps { @@ -24,17 +24,14 @@ export interface FixtureStartDeps { } export class FixturePlugin implements Plugin { - public setup( - core: CoreSetup, - { features, actions, alerting }: FixtureSetupDeps - ) { + public setup(core: CoreSetup, { features, actions, alerts }: FixtureSetupDeps) { features.registerFeature({ - id: 'alerting', - name: 'Alerting', - app: ['alerting', 'kibana'], + id: 'alerts', + name: 'Alerts', + app: ['alerts', 'kibana'], privileges: { all: { - app: ['alerting', 'kibana'], + app: ['alerts', 'kibana'], savedObject: { all: ['alert'], read: [], @@ -43,7 +40,7 @@ export class FixturePlugin implements Plugin { - const pluginPath = plugin ? `/${plugin}` : ''; return this.supertest - .delete(`${getUrlPrefix(spaceId)}/api${pluginPath}/${type}/${id}`) + .delete(`${getUrlPrefix(spaceId)}/api/${plugin}/${type}/${id}`) .set('kbn-xsrf', 'foo') .expect(204); }) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts b/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts index d58fcd29e29fc..c72ee6a192bf2 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts @@ -47,7 +47,7 @@ const GlobalRead: User = { kibana: [ { feature: { - alerting: ['read'], + alerts: ['read'], actions: ['read'], }, spaces: ['*'], @@ -75,7 +75,7 @@ const Space1All: User = { kibana: [ { feature: { - alerting: ['all'], + alerts: ['all'], actions: ['all'], }, spaces: ['space1'], diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts index 785285f6d455c..45491aa2d28fc 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts @@ -121,7 +121,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -142,7 +142,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth .get(`${getUrlPrefix(space.id)}/api/actions`) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index 02cd661cbaf04..ab58a205f9d47 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -336,7 +336,7 @@ instanceStateValue: true const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -374,7 +374,7 @@ instanceStateValue: true case 'superuser at space1': case 'space_1_all at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); // Wait for the task to be attempted once and idle const scheduledActionTask = await retry.try(async () => { @@ -428,7 +428,7 @@ instanceStateValue: true const testStart = new Date(); const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -457,7 +457,7 @@ instanceStateValue: true break; case 'space_1_all at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); // Wait for test.authorization to index a document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('alert:test.authorization', reference); @@ -490,7 +490,7 @@ instanceStateValue: true break; case 'superuser at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); // Wait for test.authorization to index a document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('alert:test.authorization', reference); @@ -532,7 +532,7 @@ instanceStateValue: true .expect(200); objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -571,7 +571,7 @@ instanceStateValue: true break; case 'space_1_all at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); // Ensure test.authorization indexed 1 document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('action:test.authorization', reference); @@ -604,7 +604,7 @@ instanceStateValue: true break; case 'superuser at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); // Ensure test.authorization indexed 1 document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('action:test.authorization', reference); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index ad9fd117c3604..4ca943f3e188a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -43,7 +43,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { .expect(200); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -72,7 +72,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { case 'superuser at space1': case 'space_1_all at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); expect(response.body).to.eql({ id: response.body.id, name: 'abc', @@ -126,7 +126,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when an alert is disabled ', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(getTestAlertData({ enabled: false })); @@ -145,7 +145,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { case 'superuser at space1': case 'space_1_all at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); expect(response.body.scheduledTaskId).to.eql(undefined); break; default: @@ -155,7 +155,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when alert type is unregistered', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -191,7 +191,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when payload is empty and invalid', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({}); @@ -223,7 +223,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it(`should handle create alert request appropriately when params isn't valid`, async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -260,7 +260,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when interval schedule is wrong syntax', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(getTestAlertData(getTestAlertData({ schedule: { interval: '10x' } }))); @@ -292,7 +292,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when interval schedule is 0', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(getTestAlertData(getTestAlertData({ schedule: { interval: '0s' } }))); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts index 593ae574e6f34..6f8ae010b9cd8 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts @@ -32,13 +32,13 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle delete alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -52,7 +52,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { error: 'Not Found', message: 'Not Found', }); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); // Ensure task still exists await getScheduledTask(createdAlert.scheduledTaskId); break; @@ -74,14 +74,14 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it(`shouldn't delete alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix('other')}/api/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -111,7 +111,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it('should still be able to delete alert when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); @@ -129,7 +129,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { .expect(200); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -143,7 +143,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { error: 'Not Found', message: 'Not Found', }); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); // Ensure task still exists await getScheduledTask(createdAlert.scheduledTaskId); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts index dbbccba70a715..589942a7ac11c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts @@ -40,11 +40,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte describe(scenario.id, () => { it('should handle disable alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getDisableRequest(createdAlert.id); @@ -86,11 +86,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it('should still be able to disable alert when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest .put( @@ -144,11 +144,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it(`shouldn't disable alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix('other')}/api/alert`) + .post(`${getUrlPrefix('other')}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add('other', createdAlert.id, 'alert', undefined); + objectRemover.add('other', createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getDisableRequest(createdAlert.id); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts index 611556aaf1fef..8cb0eeb0092a3 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts @@ -40,11 +40,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex describe(scenario.id, () => { it('should handle enable alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getEnableRequest(createdAlert.id); @@ -64,7 +64,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); @@ -91,11 +91,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it('should still be able to enable alert when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest .put( @@ -127,7 +127,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); @@ -154,11 +154,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it(`shouldn't enable alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix('other')}/api/alert`) + .post(`${getUrlPrefix('other')}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add('other', createdAlert.id, 'alert', undefined); + objectRemover.add('other', createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getEnableRequest(createdAlert.id); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index 1c4d684eb78de..5fe9edeb91ec9 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -24,15 +24,17 @@ export default function createFindTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle find alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth .get( - `${getUrlPrefix(space.id)}/api/alert/_find?search=test.noop&search_fields=alertTypeId` + `${getUrlPrefix( + space.id + )}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` ) .auth(user.username, user.password); @@ -95,7 +97,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -110,13 +112,13 @@ export default function createFindTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth .get( `${getUrlPrefix( space.id - )}/api/alert/_find?filter=alert.attributes.actions:{ actionTypeId: test.noop }` + )}/api/alerts/_find?filter=alert.attributes.actions:{ actionTypeId: test.noop }` ) .auth(user.username, user.password); @@ -174,15 +176,15 @@ export default function createFindTests({ getService }: FtrProviderContext) { it(`shouldn't find alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth .get( - `${getUrlPrefix('other')}/api/alert/_find?search=test.noop&search_fields=alertTypeId` + `${getUrlPrefix('other')}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` ) .auth(user.username, user.password); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts index 5800273dce75d..a203b7d7c151b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts @@ -24,14 +24,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle get alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .auth(user.username, user.password); switch (scenario.id) { @@ -78,14 +78,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { it(`shouldn't get alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix('other')}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}`) .auth(user.username, user.password); expect(response.statusCode).to.eql(404); @@ -114,7 +114,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it(`should handle get alert request appropriately when alert doesn't exist`, async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/1`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/1`) .auth(user.username, user.password); switch (scenario.id) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts index 42a6b36df0f97..fd071bd55b377 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts @@ -24,14 +24,14 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont describe(scenario.id, () => { it('should handle getAlertState alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}/state`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/state`) .auth(user.username, user.password); switch (scenario.id) { @@ -57,14 +57,14 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it(`shouldn't getAlertState for an alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix('other')}/api/alert/${createdAlert.id}/state`) + .get(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}/state`) .auth(user.username, user.password); expect(response.statusCode).to.eql(404); @@ -93,7 +93,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it(`should handle getAlertState request appropriately when alert doesn't exist`, async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/1/state`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/1/state`) .auth(user.username, user.password); switch (scenario.id) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts index 91b0ca0a37c92..f14f66f66fe2f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { - describe('Alerting', () => { + describe('Alerts', () => { loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./delete')); loadTestFile(require.resolve('./disable')); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts index 4f6b26dfb67fa..dd31e2dbbb5b8 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts @@ -19,7 +19,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should return 200 with list of alert types', async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/types`) + .get(`${getUrlPrefix(space.id)}/api/alerts/list_alert_types`) .auth(user.username, user.password); switch (scenario.id) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts index 0196615629e23..2416bc2ea1d12 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts @@ -32,11 +32,11 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) describe(scenario.id, () => { it('should handle mute alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getMuteAllRequest(createdAlert.id); @@ -56,7 +56,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts index 0c05dbdf55842..c59b9f4503a03 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts @@ -32,11 +32,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider describe(scenario.id, () => { it('should handle mute alert instance request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getMuteInstanceRequest(createdAlert.id, '1'); @@ -56,7 +56,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); @@ -76,14 +76,16 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider it('should handle mute alert instance request appropriately and not duplicate mutedInstanceIds when muting an instance already muted', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest - .post(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}/alert_instance/1/_mute`) + .post( + `${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/alert_instance/1/_mute` + ) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -105,7 +107,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts index ebe9f1f645ed7..fd22752ccc11a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts @@ -32,14 +32,14 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex describe(scenario.id, () => { it('should handle unmute alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest - .post(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}/_mute_all`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/_mute_all`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -61,7 +61,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts index 7142fd7d91adf..72b524282354a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts @@ -32,14 +32,16 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider describe(scenario.id, () => { it('should handle unmute alert instance request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest - .post(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}/alert_instance/1/_mute`) + .post( + `${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/alert_instance/1/_mute` + ) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -61,7 +63,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index 0af1e22584643..2bcc035beb7a9 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -39,11 +39,11 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle update alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const updatedData = { name: 'bcd', @@ -56,7 +56,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { throttle: '1m', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -110,11 +110,11 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should still be able to update when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest .put( @@ -139,7 +139,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { throttle: '1m', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -193,14 +193,14 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it(`shouldn't update alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix('other')}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -240,14 +240,14 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when attempting to change alert type', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -289,7 +289,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when payload is empty and invalid', async () => { const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/1`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({}); @@ -321,7 +321,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it(`should handle update alert request appropriately when alertTypeConfig isn't valid`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -332,10 +332,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -375,7 +375,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when interval schedule is wrong syntax', async () => { const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/1`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -413,7 +413,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle updates to an alert schedule by rescheduling the underlying task', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -421,7 +421,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await retry.try(async () => { const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; @@ -441,7 +441,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { throttle: '1m', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts index 6349919c15cd2..bf72b970dc0f1 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts @@ -32,11 +32,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte describe(scenario.id, () => { it('should handle update alert api key request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getUpdateApiKeyRequest(createdAlert.id); @@ -56,7 +56,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); @@ -76,11 +76,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it('should still be able to update API key when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', undefined); + objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); await supertest .put( @@ -112,7 +112,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); @@ -132,11 +132,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it(`shouldn't update alert api key from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix('other')}/api/alert`) + .post(`${getUrlPrefix('other')}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add('other', createdAlert.id, 'alert', undefined); + objectRemover.add('other', createdAlert.id, 'alert', 'alerts'); const response = await alertUtils.getUpdateApiKeyRequest(createdAlert.id); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts index d3c914942bd90..8ffe65a8ebb48 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts @@ -186,7 +186,7 @@ instanceStateValue: true const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -211,7 +211,7 @@ instanceStateValue: true ); expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); const scheduledActionTask = await retry.try(async () => { const searchResult = await es.search({ index: '.kibana_task_manager', @@ -255,7 +255,7 @@ instanceStateValue: true it('should have proper callCluster and savedObjectsClient authorization for alert type executor', async () => { const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -271,7 +271,7 @@ instanceStateValue: true ); expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); const alertTestRecord = ( await esTestIndexTool.waitForDocs('alert:test.authorization', reference) )[0]; @@ -301,7 +301,7 @@ instanceStateValue: true .expect(200); objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -327,7 +327,7 @@ instanceStateValue: true ); expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', undefined); + objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); const actionTestRecord = ( await esTestIndexTool.waitForDocs('action:test.authorization', reference) )[0]; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts index 353f7d02f6b0b..8412c09eefcda 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts @@ -342,7 +342,7 @@ export default function alertTests({ getService }: FtrProviderContext) { }; const { status, body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send({ name: params.name, @@ -372,7 +372,7 @@ export default function alertTests({ getService }: FtrProviderContext) { expect(status).to.be(200); const alertId = createdAlert.id; - objectRemover.add(Spaces.space1.id, alertId, 'alert', undefined); + objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); return alertId; } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index b10c356cf40d5..fa256712a012b 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -39,7 +39,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { .expect(200); const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -54,7 +54,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { ); expect(response.status).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, response.body.id, 'alert', 'alerts'); expect(response.body).to.eql({ id: response.body.id, name: 'abc', @@ -104,12 +104,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when an alert is disabled ', async () => { const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })); expect(response.status).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, response.body.id, 'alert', 'alerts'); expect(response.body.scheduledTaskId).to.eql(undefined); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts index 3aea982f948ea..e9dfe1607d32d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts @@ -28,13 +28,13 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it('should handle delete alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); await supertest - .delete(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -48,13 +48,13 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it(`shouldn't delete alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); await supertest - .delete(`${getUrlPrefix(Spaces.other.id)}/api/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(404, { statusCode: 404, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts index 7152a76fa167f..afa4f03e23b30 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts @@ -35,11 +35,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it('should handle disable alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.disable(createdAlert.id); @@ -61,11 +61,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it(`shouldn't disable alert from another space`, async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.other.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.getDisableRequest(createdAlert.id).expect(404, { statusCode: 404, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts index 3d556d0936022..05b212bb064f3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts @@ -35,16 +35,16 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it('should handle enable alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.enable(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(typeof updatedAlert.scheduledTaskId).to.eql('string'); @@ -67,11 +67,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it(`shouldn't enable alert from another space`, async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.other.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.getEnableRequest(createdAlert.id).expect(404, { statusCode: 404, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index f57b136b9637a..06f27d666c3da 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -20,16 +20,16 @@ export default function createFindTests({ getService }: FtrProviderContext) { it('should handle find alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); const response = await supertest.get( `${getUrlPrefix( Spaces.space1.id - )}/api/alert/_find?search=test.noop&search_fields=alertTypeId` + )}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` ); expect(response.status).to.eql(200); @@ -63,17 +63,17 @@ export default function createFindTests({ getService }: FtrProviderContext) { it(`shouldn't find alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await supertest .get( `${getUrlPrefix( Spaces.other.id - )}/api/alert/_find?search=test.noop&search_fields=alertTypeId` + )}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` ) .expect(200, { page: 1, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index 6b216d2ba265f..ff671e16654b5 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -20,14 +20,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('should handle get alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}` ); expect(response.status).to.eql(200); @@ -57,14 +57,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { it(`shouldn't find alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await supertest - .get(`${getUrlPrefix(Spaces.other.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert/${createdAlert.id}`) .expect(404, { statusCode: 404, error: 'Not Found', @@ -73,7 +73,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }); it(`should handle get alert request appropriately when alert doesn't exist`, async () => { - await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/1`).expect(404, { + await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/1`).expect(404, { statusCode: 404, error: 'Not Found', message: 'Saved object [alert/1] not found', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts index 06f5f5542780c..d3f08d7c509a0 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts @@ -21,14 +21,14 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it('should handle getAlertState request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}/state` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state` ); expect(response.status).to.eql(200); @@ -37,7 +37,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it('should fetch updated state', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send({ enabled: true, @@ -51,12 +51,12 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont params: {}, }) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); // wait for alert to actually execute await retry.try(async () => { const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}/state` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state` ); expect(response.status).to.eql(200); @@ -65,7 +65,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont }); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}/state` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state` ); expect(response.body.alertTypeState.runCount).to.greaterThan(0); @@ -79,11 +79,13 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont }); it(`should handle getAlertState request appropriately when alert doesn't exist`, async () => { - await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/1/state`).expect(404, { - statusCode: 404, - error: 'Not Found', - message: 'Saved object [alert/1] not found', - }); + await supertest + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/1/state`) + .expect(404, { + statusCode: 404, + error: 'Not Found', + message: 'Saved object [alert/1] not found', + }); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts index 845a6f7955739..aef87eefba2ad 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts @@ -15,7 +15,9 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { describe('list_alert_types', () => { it('should return 200 with list of alert types', async () => { - const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + ); expect(response.status).to.eql(200); const fixtureAlertType = response.body.find((alertType: any) => alertType.id === 'test.noop'); expect(fixtureAlertType).to.eql({ @@ -32,7 +34,9 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { }); it('should return actionVariables with both context and state', async () => { - const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + ); expect(response.status).to.eql(200); const fixtureAlertType = response.body.find( @@ -46,7 +50,9 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { }); it('should return actionVariables with just context', async () => { - const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + ); expect(response.status).to.eql(200); const fixtureAlertType = response.body.find( @@ -60,7 +66,9 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { }); it('should return actionVariables with just state', async () => { - const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + ); expect(response.status).to.eql(200); const fixtureAlertType = response.body.find( diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts index b2ba38ac98470..f881d0c677bb5 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts @@ -27,16 +27,16 @@ export default function createMuteTests({ getService }: FtrProviderContext) { it('should handle mute alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.muteAll(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.muteAll).to.eql(true); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts index d9f52d3321e32..ca0d72aedf7a1 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts @@ -27,16 +27,16 @@ export default function createMuteInstanceTests({ getService }: FtrProviderConte it('should handle mute alert instance request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.muteInstance(createdAlert.id, '1'); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mutedInstanceIds).to.eql(['1']); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts index 7c5f1e0a62130..1df99540903d0 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts @@ -27,17 +27,17 @@ export default function createUnmuteTests({ getService }: FtrProviderContext) { it('should handle unmute alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.muteAll(createdAlert.id); await alertUtils.unmuteAll(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.muteAll).to.eql(false); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts index 86464c3d6bb64..332842ce8015f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts @@ -27,17 +27,17 @@ export default function createUnmuteInstanceTests({ getService }: FtrProviderCon it('should handle unmute alert instance request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.muteInstance(createdAlert.id, '1'); await alertUtils.unmuteInstance(createdAlert.id, '1'); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mutedInstanceIds).to.eql([]); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts index fc0aeb71d9066..b01a1b140f2d6 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts @@ -20,11 +20,11 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); const updatedData = { name: 'bcd', @@ -37,7 +37,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { throttle: '1m', }; const response = await supertest - .put(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .send(updatedData) .expect(200); @@ -75,14 +75,14 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it(`shouldn't update alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await supertest - .put(`${getUrlPrefix(Spaces.other.id)}/api/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .send({ name: 'bcd', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts index 9c7b4dcc8b1a3..93f91bdc73150 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts @@ -31,16 +31,16 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it('should handle update alert api key appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.updateApiKey(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.apiKeyOwner).to.eql(null); @@ -56,11 +56,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it(`shouldn't update alert api key from another space`, async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.other.id)}/api/alert`) + .post(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', undefined); + objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', 'alerts'); await alertUtils.getUpdateApiKeyRequest(createdAlert.id).expect(404, { statusCode: 404, diff --git a/x-pack/test/api_integration/apis/endpoint/alerts/index.ts b/x-pack/test/api_integration/apis/endpoint/alerts/index.ts index ecdee09ce7edf..155513aefc609 100644 --- a/x-pack/test/api_integration/apis/endpoint/alerts/index.ts +++ b/x-pack/test/api_integration/apis/endpoint/alerts/index.ts @@ -70,7 +70,7 @@ export default function ({ getService }: FtrProviderContext) { let nullableEventId = ''; - describe.skip('Endpoint alert API', () => { + describe('Endpoint alert API', () => { describe('when data is in elasticsearch', () => { before(async () => { await esArchiver.load('endpoint/alerts/api_feature'); diff --git a/x-pack/test/api_integration/apis/endpoint/alerts/index_pattern.ts b/x-pack/test/api_integration/apis/endpoint/alerts/index_pattern.ts index df1cbcfe28e7b..ad9f4463c6419 100644 --- a/x-pack/test/api_integration/apis/endpoint/alerts/index_pattern.ts +++ b/x-pack/test/api_integration/apis/endpoint/alerts/index_pattern.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - describe.skip('Endpoint index pattern API', () => { + describe('Endpoint index pattern API', () => { it('should retrieve the index pattern for events', async () => { const { body } = await supertest.get('/api/endpoint/index_pattern/events').expect(200); expect(body.indexPattern).to.eql('events-endpoint-*'); @@ -17,7 +17,12 @@ export default function ({ getService }: FtrProviderContext) { it('should retrieve the index pattern for metadata', async () => { const { body } = await supertest.get('/api/endpoint/index_pattern/metadata').expect(200); - expect(body.indexPattern).to.eql('metrics-endpoint-*'); + expect(body.indexPattern).to.eql('metrics-endpoint.metadata-*'); + }); + + it('should retrieve the index pattern for policy', async () => { + const { body } = await supertest.get('/api/endpoint/index_pattern/policy').expect(200); + expect(body.indexPattern).to.eql('metrics-endpoint.policy-*'); }); it('should not retrieve the index pattern for an invalid key', async () => { diff --git a/x-pack/test/api_integration/apis/endpoint/metadata.ts b/x-pack/test/api_integration/apis/endpoint/metadata.ts index c01919f60a922..5c4bb52b8d9e2 100644 --- a/x-pack/test/api_integration/apis/endpoint/metadata.ts +++ b/x-pack/test/api_integration/apis/endpoint/metadata.ts @@ -14,7 +14,7 @@ const numberOfHostsInFixture = 3; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); - describe.skip('test metadata api', () => { + describe('test metadata api', () => { describe('POST /api/endpoint/metadata when index is empty', () => { it('metadata api should return empty result when index is empty', async () => { await esArchiver.unload('endpoint/metadata/api_feature'); diff --git a/x-pack/test/api_integration/apis/fleet/agent_flow.ts b/x-pack/test/api_integration/apis/fleet/agent_flow.ts new file mode 100644 index 0000000000000..e9e9f0e0208e4 --- /dev/null +++ b/x-pack/test/api_integration/apis/fleet/agent_flow.ts @@ -0,0 +1,145 @@ +/* + * 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 expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; +import { setupIngest, getSupertestWithoutAuth } from './agents/services'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + const supertestWithoutAuth = getSupertestWithoutAuth(providerContext); + const esClient = getService('es'); + + describe('fleet_agent_flow', () => { + before(async () => { + await esArchiver.load('empty_kibana'); + }); + setupIngest(providerContext); + after(async () => { + await esArchiver.unload('empty_kibana'); + }); + + it('should work', async () => { + // 1. Get enrollment token + const { body: enrollmentApiKeysResponse } = await supertest + .get(`/api/ingest_manager/fleet/enrollment-api-keys`) + .expect(200); + + expect(enrollmentApiKeysResponse.list).length(1); + const { body: enrollmentApiKeyResponse } = await supertest + .get( + `/api/ingest_manager/fleet/enrollment-api-keys/${enrollmentApiKeysResponse.list[0].id}` + ) + .expect(200); + + expect(enrollmentApiKeyResponse.item).to.have.key('api_key'); + const enrollmentAPIToken = enrollmentApiKeyResponse.item.api_key; + // 2. Enroll agent + const { body: enrollmentResponse } = await supertestWithoutAuth + .post(`/api/ingest_manager/fleet/agents/enroll`) + .set('kbn-xsrf', 'xxx') + .set('Authorization', `ApiKey ${enrollmentAPIToken}`) + .send({ + type: 'PERMANENT', + metadata: { + local: {}, + user_provided: {}, + }, + }) + .expect(200); + expect(enrollmentResponse.success).to.eql(true); + + const agentAccessAPIKey = enrollmentResponse.item.access_api_key; + + // 3. agent checkin + const { body: checkinApiResponse } = await supertestWithoutAuth + .post(`/api/ingest_manager/fleet/agents/${enrollmentResponse.item.id}/checkin`) + .set('kbn-xsrf', 'xx') + .set('Authorization', `ApiKey ${agentAccessAPIKey}`) + .send({ + events: [], + }) + .expect(200); + + expect(checkinApiResponse.success).to.eql(true); + expect(checkinApiResponse.actions).length(1); + expect(checkinApiResponse.actions[0].type).be('CONFIG_CHANGE'); + const configChangeAction = checkinApiResponse.actions[0]; + const defaultOutputApiKey = configChangeAction.data.config.outputs.default.api_key; + + // 4. ack actions + const { body: ackApiResponse } = await supertestWithoutAuth + .post(`/api/ingest_manager/fleet/agents/${enrollmentResponse.item.id}/acks`) + .set('Authorization', `ApiKey ${agentAccessAPIKey}`) + .set('kbn-xsrf', 'xx') + + .send({ + events: [ + { + type: 'ACTION_RESULT', + subtype: 'CONFIG', + timestamp: '2019-01-04T14:32:03.36764-05:00', + action_id: configChangeAction.id, + agent_id: enrollmentResponse.item.id, + message: 'hello', + payload: 'payload', + }, + ], + }) + .expect(200); + expect(ackApiResponse.success).to.eql(true); + + // 4. second agent checkin + const { body: secondCheckinApiResponse } = await supertestWithoutAuth + .post(`/api/ingest_manager/fleet/agents/${enrollmentResponse.item.id}/checkin`) + .set('kbn-xsrf', 'xx') + .set('Authorization', `ApiKey ${agentAccessAPIKey}`) + .send({ + events: [], + }) + .expect(200); + expect(secondCheckinApiResponse.success).to.eql(true); + expect(secondCheckinApiResponse.actions).length(0); + + // 5. unenroll agent + const { body: unenrollResponse } = await supertest + .post(`/api/ingest_manager/fleet/agents/${enrollmentResponse.item.id}/unenroll`) + .set('kbn-xsrf', 'xx') + .expect(200); + expect(unenrollResponse.success).to.eql(true); + + // 6. Checkin after unenrollment + await supertestWithoutAuth + .post(`/api/ingest_manager/fleet/agents/${enrollmentResponse.item.id}/checkin`) + .set('kbn-xsrf', 'xx') + .set('Authorization', `ApiKey ${agentAccessAPIKey}`) + .send({ + events: [], + }) + .expect(401); + + // very api key are invalidated + const { + body: { api_keys: accessAPIKeys }, + } = await esClient.security.getApiKey({ + id: Buffer.from(agentAccessAPIKey, 'base64').toString('utf8').split(':')[0], + }); + expect(accessAPIKeys).length(1); + expect(accessAPIKeys[0].invalidated).eql(true); + + const { + body: { api_keys: outputAPIKeys }, + } = await esClient.security.getApiKey({ + id: defaultOutputApiKey.split(':')[0], + }); + expect(outputAPIKeys).length(1); + expect(outputAPIKeys[0].invalidated).eql(true); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/fleet/index.js b/x-pack/test/api_integration/apis/fleet/index.js index 289af867a10f8..a8c026ac6a1bd 100644 --- a/x-pack/test/api_integration/apis/fleet/index.js +++ b/x-pack/test/api_integration/apis/fleet/index.js @@ -17,5 +17,6 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./install')); loadTestFile(require.resolve('./agents/actions')); loadTestFile(require.resolve('./setup')); + loadTestFile(require.resolve('./agent_flow')); }); } diff --git a/x-pack/test/api_integration/apis/maps/fonts_api.js b/x-pack/test/api_integration/apis/maps/fonts_api.js new file mode 100644 index 0000000000000..d367fb6a0610b --- /dev/null +++ b/x-pack/test/api_integration/apis/maps/fonts_api.js @@ -0,0 +1,21 @@ +/* + * 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 expect from '@kbn/expect'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + + describe('fonts', () => { + it('should return fonts', async () => { + const resp = await supertest + .get(`/api/maps/fonts/Open%20Sans%20Regular,Arial%20Unicode%20MS%20Regular/0-255`) + .expect(200); + + expect(resp.body.length).to.be(74696); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/maps/index.js b/x-pack/test/api_integration/apis/maps/index.js index a6267b9fd0cea..f9dff19229645 100644 --- a/x-pack/test/api_integration/apis/maps/index.js +++ b/x-pack/test/api_integration/apis/maps/index.js @@ -13,6 +13,7 @@ export default function ({ loadTestFile, getService }) { }); describe('', () => { + loadTestFile(require.resolve('./fonts_api')); loadTestFile(require.resolve('./index_settings')); loadTestFile(require.resolve('./migrations')); }); diff --git a/x-pack/test/case_api_integration/common/config.ts b/x-pack/test/case_api_integration/common/config.ts index 9eb62c2fe07b0..45b34b7d26940 100644 --- a/x-pack/test/case_api_integration/common/config.ts +++ b/x-pack/test/case_api_integration/common/config.ts @@ -78,7 +78,6 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) 'some.non.existent.com', ])}`, `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, - '--xpack.alerting.enabled=true', '--xpack.eventLog.logEntries=true', ...disabledPlugins.map((key) => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, 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 b64db5eaeffac..cbf1f3e1af2df 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 @@ -182,9 +182,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`does not allow navigation to advanced settings; redirects to management home`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/settings', { + await PageObjects.common.navigateToUrl('management', 'kibana/settings', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('managementHome', { timeout: config.get('timeouts.waitFor'), diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts index 7e4aa6e375aef..f7991e62fdaa9 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts @@ -47,10 +47,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`allows settings to be changed`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/settings', { + await PageObjects.common.navigateToUrl('management', 'kibana/settings', { basePath: `/s/custom_space`, ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await PageObjects.settings.setAdvancedSettingsSelect('dateFormat:tz', 'America/Phoenix'); const advancedSetting = await PageObjects.settings.getAdvancedSettings('dateFormat:tz'); @@ -77,10 +78,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`redirects to management home`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/settings', { + await PageObjects.common.navigateToUrl('management', 'kibana/settings', { basePath: `/s/custom_space`, ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('managementHome', { timeout: config.get('timeouts.waitFor'), diff --git a/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js b/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js index 612556b0c4728..00183113a4d59 100644 --- a/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js +++ b/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js @@ -115,7 +115,11 @@ export default function ({ getService, getPageObjects }) { }); it('shows only the dashboard app link', async () => { - await security.testUser.setRoles(['test_logstash_reader', 'kibana_dashboard_only_user']); + await security.testUser.setRoles( + ['test_logstash_reader', 'kibana_dashboard_only_user'], + false + ); + await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.security.forceLogout(); await PageObjects.security.login('test_user', 'changeme'); diff --git a/x-pack/test/functional/apps/graph/graph.ts b/x-pack/test/functional/apps/graph/graph.ts index d9e8b22c45d50..803e5e8f80d70 100644 --- a/x-pack/test/functional/apps/graph/graph.ts +++ b/x-pack/test/functional/apps/graph/graph.ts @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - describe('graph', function () { + // FLAKY: https://github.com/elastic/kibana/issues/53749 + describe.skip('graph', function () { before(async () => { await browser.setWindowSize(1600, 1000); log.debug('load graph/secrepo data'); 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 1d45d00cbc894..cd892c4424290 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 @@ -187,9 +187,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`does not allow navigation to Index Patterns; redirects to management home`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/indexPatterns', { + await PageObjects.common.navigateToUrl('management', 'kibana/indexPatterns', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('managementHome', { timeout: config.get('timeouts.waitFor'), diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts index 7e82dfab1d7a3..29929a978c8d8 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts @@ -70,10 +70,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`redirects to management home`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/indexPatterns', { + await PageObjects.common.navigateToUrl('management', 'kibana/indexPatterns', { basePath: `/s/custom_space`, ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('managementHome', { timeout: config.get('timeouts.waitFor'), diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 39da6d8866e9e..239da3ea8c693 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -64,7 +64,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.getActions().move({ x: 5, y: 5, origin: el._webElement }).click().perform(); } - describe('lens smokescreen tests', () => { + // FLAKY: https://github.com/elastic/kibana/issues/67838 + describe.skip('lens smokescreen tests', () => { it('should allow editing saved visualizations', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens'); diff --git a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js index 9b5b82efe6f51..a996910d4787a 100644 --- a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js +++ b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js @@ -32,8 +32,9 @@ export default function ({ getPageObjects, getService }) { await testSubjects.click('mapTooltipCreateFilterButton'); await testSubjects.click('applyFiltersPopoverButton'); - const hasSourceFilter = await filterBar.hasFilter('name', 'charlie'); - expect(hasSourceFilter).to.be(true); + // TODO: Fix me #64861 + // const hasSourceFilter = await filterBar.hasFilter('name', 'charlie'); + // expect(hasSourceFilter).to.be(true); const hasJoinFilter = await filterBar.hasFilter('shape_name', 'charlie'); expect(hasJoinFilter).to.be(true); diff --git a/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts b/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts index 383f97cb2ce43..9969608bd2a45 100644 --- a/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts +++ b/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts @@ -111,11 +111,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('edit visualization', () => { before(async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', + await PageObjects.common.navigateToUrl( + 'management', + 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', { shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, } ); }); @@ -229,11 +230,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('edit visualization', () => { before(async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', + await PageObjects.common.navigateToUrl( + 'management', + 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', { shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, } ); await testSubjects.existOrFail('savedObjectsEdit'); @@ -302,9 +304,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('listing', () => { it('redirects to Kibana home', async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/objects', { + await PageObjects.common.navigateToUrl('management', 'kibana/objects', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await PageObjects.header.waitUntilLoadingHasFinished(); await testSubjects.existOrFail('homeApp'); @@ -313,12 +316,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('edit visualization', () => { it('redirects to Kibana home', async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', + await PageObjects.common.navigateToUrl( + 'management', + 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', { shouldLoginIfPrompted: false, ensureCurrentUrl: false, + shouldUseHashForSubUrl: false, } ); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/functional/apps/security/management.js b/x-pack/test/functional/apps/security/management.js index d761f4d1f785c..c263b26406857 100644 --- a/x-pack/test/functional/apps/security/management.js +++ b/x-pack/test/functional/apps/security/management.js @@ -5,13 +5,6 @@ */ import expect from '@kbn/expect'; -import { - USERS_PATH, - EDIT_USERS_PATH, - ROLES_PATH, - EDIT_ROLES_PATH, - CLONE_ROLES_PATH, -} from '../../../../plugins/security/public/management/management_urls'; export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); @@ -19,6 +12,13 @@ export default function ({ getService, getPageObjects }) { const browser = getService('browser'); const PageObjects = getPageObjects(['security', 'settings', 'common', 'header']); + const USERS_PATH = 'security/users'; + const EDIT_USERS_PATH = `${USERS_PATH}/edit`; + + const ROLES_PATH = 'security/roles'; + const EDIT_ROLES_PATH = `${ROLES_PATH}/edit`; + const CLONE_ROLES_PATH = `${ROLES_PATH}/clone`; + // FLAKY: https://github.com/elastic/kibana/issues/61173 describe.skip('Management', function () { this.tags(['skipFirefox']); diff --git a/x-pack/test/functional/apps/security/role_mappings.ts b/x-pack/test/functional/apps/security/role_mappings.ts index 16d8fdd4b3c1e..60c166d837933 100644 --- a/x-pack/test/functional/apps/security/role_mappings.ts +++ b/x-pack/test/functional/apps/security/role_mappings.ts @@ -83,17 +83,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('displays an error and returns to the listing page when navigating to a role mapping which does not exist', async () => { - await pageObjects.common.navigateToActualUrl( - 'kibana', - '#/management/security/role_mappings/edit/i-do-not-exist', - { ensureCurrentUrl: false } + await pageObjects.common.navigateToUrl( + 'management', + 'security/role_mappings/edit/i-do-not-exist', + { ensureCurrentUrl: false, shouldUseHashForSubUrl: false } ); await testSubjects.existOrFail('errorLoadingRoleMappingEditorToast'); const url = parse(await browser.getCurrentUrl()); - expect(url.hash).to.eql('#/management/security/role_mappings'); + expect(url.pathname).to.eql('/app/management/security/role_mappings/'); }); describe('with role mappings', () => { diff --git a/x-pack/test/functional/apps/security/trial_license/login_selector.ts b/x-pack/test/functional/apps/security/trial_license/login_selector.ts index 95e68eb9a1f6e..e0b776cd123c1 100644 --- a/x-pack/test/functional/apps/security/trial_license/login_selector.ts +++ b/x-pack/test/functional/apps/security/trial_license/login_selector.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { parse } from 'url'; -import { USERS_PATH } from '../../../../../plugins/security/public/management/management_urls'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -42,9 +41,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('can login with Login Form preserving original URL', async () => { - await PageObjects.common.navigateToActualUrl('kibana', USERS_PATH, { + await PageObjects.common.navigateToUrl('management', 'security/users', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await PageObjects.common.waitUntilUrlIncludes('next='); @@ -52,14 +52,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // We need to make sure that both path and hash are respected. const currentURL = parse(await browser.getCurrentUrl()); - expect(currentURL.pathname).to.eql('/app/kibana'); - expect(currentURL.hash).to.eql(`#${USERS_PATH}`); + + expect(currentURL.pathname).to.eql('/app/management/security/users'); }); it('can login with SSO preserving original URL', async () => { - await PageObjects.common.navigateToActualUrl('kibana', USERS_PATH, { + await PageObjects.common.navigateToUrl('management', 'security/users', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await PageObjects.common.waitUntilUrlIncludes('next='); @@ -67,8 +68,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // We need to make sure that both path and hash are respected. const currentURL = parse(await browser.getCurrentUrl()); - expect(currentURL.pathname).to.eql('/app/kibana'); - expect(currentURL.hash).to.eql(`#${USERS_PATH}`); + expect(currentURL.pathname).to.eql('/app/management/security/users'); }); it('should show toast with error if SSO fails', async () => { diff --git a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts index d43d56ae22e36..d904c407b060b 100644 --- a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts +++ b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts @@ -65,32 +65,31 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`can navigate to spaces grid page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces', { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('spaces-grid-page'); }); it(`can navigate to create new space page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces/create', { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/create', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('spaces-edit-page'); }); it(`can navigate to edit space page`, async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - 'management/kibana/spaces/edit/default', - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); await testSubjects.existOrFail('spaces-edit-page'); }); @@ -139,39 +138,38 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`doesn't display Spaces management section`, async () => { await PageObjects.settings.navigateTo(); - await testSubjects.existOrFail('management-landing'); // this ensures we've gotten to the management page + await testSubjects.existOrFail('managementHome'); // this ensures we've gotten to the management page await testSubjects.missingOrFail('spaces'); }); it(`can't navigate to spaces grid page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces', { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); - await testSubjects.existOrFail('management-landing'); + await testSubjects.existOrFail('managementHome'); }); it(`can't navigate to create new space page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces/create', { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/create', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); - await testSubjects.existOrFail('management-landing'); + await testSubjects.existOrFail('managementHome'); }); it(`can't navigate to edit space page`, async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - 'management/kibana/spaces/edit/default', - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); - await testSubjects.existOrFail('management-landing'); + await testSubjects.existOrFail('managementHome'); }); }); }); diff --git a/x-pack/test/functional/apps/spaces/spaces_selection.ts b/x-pack/test/functional/apps/spaces/spaces_selection.ts index d53afade9f18b..ffc88b4a5a17b 100644 --- a/x-pack/test/functional/apps/spaces/spaces_selection.ts +++ b/x-pack/test/functional/apps/spaces/spaces_selection.ts @@ -20,7 +20,8 @@ export default function spaceSelectorFunctonalTests({ 'spaceSelector', ]); - describe('Spaces', function () { + // FLAKY: https://github.com/elastic/kibana/issues/51942 + describe.skip('Spaces', function () { this.tags('includeFirefox'); describe('Space Selector', () => { before(async () => { diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 4ff9c5cd1df56..bcc7c978ca237 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -113,8 +113,7 @@ export default async function ({ readConfigFile }) { pathname: '/app/monitoring', }, logstashPipelines: { - pathname: '/app/kibana', - hash: '/management/ingest/pipelines', + pathname: '/app/management/ingest/pipelines', }, maps: { pathname: '/app/maps', @@ -150,59 +149,46 @@ export default async function ({ readConfigFile }) { pathname: '/app/ml', }, roleMappings: { - pathname: '/app/kibana', - hash: '/management/security/role_mappings', + pathname: '/app/management/security/role_mappings', }, rollupJob: { - pathname: '/app/kibana', - hash: '/management/data/rollup_jobs/', + pathname: '/app/management/data/rollup_jobs', }, apiKeys: { - pathname: '/app/kibana', - hash: '/management/security/api_keys/', + pathname: '/app/management/security/api_keys', }, licenseManagement: { - pathname: '/app/kibana', - hash: '/management/stack/license_management', + pathname: '/app/management/stack/license_management', }, indexManagement: { - pathname: '/app/kibana', - hash: '/management/data/index_management', + pathname: '/app/management/data/index_management', }, indexLifecycleManagement: { - pathname: '/app/kibana', - hash: '/management/data/index_lifecycle_management', + pathname: '/app/management/data/index_lifecycle_management', }, ingestPipelines: { - pathname: '/app/kibana', - hash: '/management/ingest/ingest_pipelines', + pathname: '/app/management/ingest/ingest_pipelines', }, snapshotRestore: { - pathname: '/app/kibana', - hash: '/management/data/snapshot_restore', - }, - crossClusterReplication: { - pathname: '/app/kibana', - hash: '/management/data/cross_cluster_replication', + pathname: '/app/management/data/snapshot_restore', }, remoteClusters: { - pathname: '/app/kibana', - hash: '/management/data/remote_clusters', + pathname: '/app/management/data/remote_clusters', + }, + crossClusterReplication: { + pathname: '/app/management/data/cross_cluster_replication', }, apm: { pathname: '/app/apm', }, watcher: { - pathname: '/app/kibana', - hash: '/management/insightsAndAlerting/watcher/watches/', + pathname: '/app/management/insightsAndAlerting/watcher/watches', }, transform: { - pathname: '/app/kibana/', - hash: '/management/data/transform', + pathname: '/app/management/data/transform', }, reporting: { - pathname: '/app/kibana/', - hash: '/management/insightsAndAlerting/reporting', + pathname: '/app/management/insightsAndAlerting/reporting', }, }, diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz index 3d4f0e11a7cc6..49082ed3bec8b 100644 Binary files a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz and b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json index f9d5de0d0a94c..ffd64ab3bb939 100644 --- a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json @@ -2,7 +2,7 @@ "type": "index", "value": { "aliases": {}, - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "mappings": { "_meta": { "version": "1.5.0-dev" diff --git a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json index d3617dc236375..0f9f86b36dec7 100644 --- a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json +++ b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json @@ -2,7 +2,7 @@ "type": "doc", "value": { "id": "3KVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579881969541, "agent": { @@ -51,7 +51,7 @@ "type": "doc", "value": { "id": "3aVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579881969541, "agent": { @@ -99,7 +99,7 @@ "type": "doc", "value": { "id": "3qVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579881969541, "agent": { @@ -145,7 +145,7 @@ "type": "doc", "value": { "id": "36VN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579878369541, "agent": { @@ -194,7 +194,7 @@ "type": "doc", "value": { "id": "4KVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579878369541, "agent": { @@ -241,7 +241,7 @@ "type": "doc", "value": { "id": "4aVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579878369541, "agent": { @@ -288,7 +288,7 @@ "type": "doc", "value": { "id": "4qVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579874769541, "agent": { @@ -336,7 +336,7 @@ "type": "doc", "value": { "id": "46VN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579874769541, "agent": { @@ -383,7 +383,7 @@ "type": "doc", "value": { "id": "5KVN2G8BYQH1gtPUuYk7", - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "source": { "@timestamp": 1579874769541, "agent": { diff --git a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json index f9d5de0d0a94c..ffd64ab3bb939 100644 --- a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json @@ -2,7 +2,7 @@ "type": "index", "value": { "aliases": {}, - "index": "metrics-endpoint-default-1", + "index": "metrics-endpoint.metadata-default-1", "mappings": { "_meta": { "version": "1.5.0-dev" diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 89ce3742adf64..13bf47676cc09 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -21,7 +21,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { async function createAlert(overwrites: Record = {}) { const { body: createdAlert } = await supertest - .post(`/api/alert`) + .post(`/api/alerts/alert`) .set('kbn-xsrf', 'foo') .send({ enabled: true, diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts index d78053cf926dc..6cb74aff95be2 100644 --- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts @@ -89,7 +89,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // put the fetch code in a retry block with a timeout. let alert: any; await retry.tryForTime(15000, async () => { - const apiResponse = await supertest.get('/api/alert/_find?search=uptime-test'); + const apiResponse = await supertest.get('/api/alerts/_find?search=uptime-test'); const alertsFromThisTest = apiResponse.body.data.filter( ({ name }: { name: string }) => name === 'uptime-test' ); @@ -129,7 +129,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { '"minimum_should_match":1}},{"bool":{"should":[{"match":{"monitor.type":"http"}}],"minimum_should_match":1}}]}}]}}]}}' ); } finally { - await supertest.delete(`/api/alert/${id}`).set('kbn-xsrf', 'true').expect(204); + await supertest.delete(`/api/alerts/alert/${id}`).set('kbn-xsrf', 'true').expect(204); } }); }); @@ -176,7 +176,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('has created a valid alert with expected parameters', async () => { let alert: any; await retry.tryForTime(15000, async () => { - const apiResponse = await supertest.get(`/api/alert/_find?search=${alertId}`); + const apiResponse = await supertest.get(`/api/alerts/_find?search=${alertId}`); const alertsFromThisTest = apiResponse.body.data.filter( ({ name }: { name: string }) => name === alertId ); @@ -204,7 +204,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(params).to.eql({}); expect(interval).to.eql('11m'); } finally { - await supertest.delete(`/api/alert/${id}`).set('kbn-xsrf', 'true').expect(204); + await supertest.delete(`/api/alerts/alert/${id}`).set('kbn-xsrf', 'true').expect(204); } }); }); diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts index b71c2cba03f21..3452caf0ccdb3 100644 --- a/x-pack/test/functional_with_es_ssl/config.ts +++ b/x-pack/test/functional_with_es_ssl/config.ts @@ -50,8 +50,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { apps: { ...xpackFunctionalConfig.get('apps'), triggersActions: { - pathname: '/app/kibana', - hash: '/management/insightsAndAlerting/triggersActions', + pathname: '/app/management/insightsAndAlerting/triggersActions', }, }, esTestCluster: { diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/kibana.json b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/kibana.json index 1715f30b82260..74f740f52a8b2 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/kibana.json +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/kibana.json @@ -3,7 +3,7 @@ "version": "1.0.0", "kibanaVersion": "kibana", "configPath": ["xpack"], - "requiredPlugins": ["alerting", "triggers_actions_ui"], + "requiredPlugins": ["alerts", "triggers_actions_ui"], "server": true, "ui": true } diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts index 4c68a3aa15b30..2bc299ede930b 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts @@ -6,21 +6,21 @@ import React from 'react'; import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; -import { PluginSetupContract as AlertingSetup } from '../../../../../../plugins/alerting/public'; -import { AlertType, SanitizedAlert } from '../../../../../../plugins/alerting/common'; +import { PluginSetupContract as AlertingSetup } from '../../../../../../plugins/alerts/public'; +import { AlertType, SanitizedAlert } from '../../../../../../plugins/alerts/common'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../../../../plugins/triggers_actions_ui/public'; export type Setup = void; export type Start = void; export interface AlertingExamplePublicSetupDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup; } export class AlertingFixturePlugin implements Plugin { - public setup(core: CoreSetup, { alerting, triggers_actions_ui }: AlertingExamplePublicSetupDeps) { - alerting.registerNavigation( + public setup(core: CoreSetup, { alerts, triggers_actions_ui }: AlertingExamplePublicSetupDeps) { + alerts.registerNavigation( 'consumer-noop', 'test.noop', (alert: SanitizedAlert, alertType: AlertType) => `/alert/${alert.id}` diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts index 123c0c550e71e..fb431351a382d 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts @@ -8,24 +8,24 @@ import { Plugin, CoreSetup } from 'kibana/server'; import { PluginSetupContract as AlertingSetup, AlertType, -} from '../../../../../../plugins/alerting/server'; +} from '../../../../../../plugins/alerts/server'; // this plugin's dependendencies export interface AlertingExampleDeps { - alerting: AlertingSetup; + alerts: AlertingSetup; } export class AlertingFixturePlugin implements Plugin { - public setup(core: CoreSetup, { alerting }: AlertingExampleDeps) { - createNoopAlertType(alerting); - createAlwaysFiringAlertType(alerting); + public setup(core: CoreSetup, { alerts }: AlertingExampleDeps) { + createNoopAlertType(alerts); + createAlwaysFiringAlertType(alerts); } public start() {} public stop() {} } -function createNoopAlertType(alerting: AlertingSetup) { +function createNoopAlertType(alerts: AlertingSetup) { const noopAlertType: AlertType = { id: 'test.noop', name: 'Test: Noop', @@ -34,10 +34,10 @@ function createNoopAlertType(alerting: AlertingSetup) { async executor() {}, producer: 'alerting', }; - alerting.registerType(noopAlertType); + alerts.registerType(noopAlertType); } -function createAlwaysFiringAlertType(alerting: AlertingSetup) { +function createAlwaysFiringAlertType(alerts: AlertingSetup) { // Alert types const alwaysFiringAlertType: any = { id: 'test.always-firing', @@ -63,5 +63,5 @@ function createAlwaysFiringAlertType(alerting: AlertingSetup) { }; }, }; - alerting.registerType(alwaysFiringAlertType); + alerts.registerType(alwaysFiringAlertType); } diff --git a/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts b/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts index 2a0d28f246765..25f4c6a932d5e 100644 --- a/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts @@ -38,7 +38,7 @@ export class Alerts { ) { this.log.debug(`creating alert ${name}`); - const { data: alert, status, statusText } = await this.axios.post(`/api/alert`, { + const { data: alert, status, statusText } = await this.axios.post(`/api/alerts/alert`, { enabled: true, name, tags, @@ -63,7 +63,7 @@ export class Alerts { public async createNoOp(name: string) { this.log.debug(`creating alert ${name}`); - const { data: alert, status, statusText } = await this.axios.post(`/api/alert`, { + const { data: alert, status, statusText } = await this.axios.post(`/api/alerts/alert`, { enabled: true, name, tags: ['foo'], @@ -96,7 +96,7 @@ export class Alerts { ) { this.log.debug(`creating alert ${name}`); - const { data: alert, status, statusText } = await this.axios.post(`/api/alert`, { + const { data: alert, status, statusText } = await this.axios.post(`/api/alerts/alert`, { enabled: true, name, tags: ['foo'], @@ -132,7 +132,7 @@ export class Alerts { public async deleteAlert(id: string) { this.log.debug(`deleting alert ${id}`); - const { data: alert, status, statusText } = await this.axios.delete(`/api/alert/${id}`); + const { data: alert, status, statusText } = await this.axios.delete(`/api/alerts/alert/${id}`); if (status !== 204) { throw new Error( `Expected status code of 204, received ${status} ${statusText}: ${util.inspect(alert)}` @@ -144,7 +144,7 @@ export class Alerts { public async getAlertState(id: string) { this.log.debug(`getting alert ${id} state`); - const { data } = await this.axios.get(`/api/alert/${id}/state`); + const { data } = await this.axios.get(`/api/alerts/alert/${id}/state`); return data; } @@ -152,7 +152,7 @@ export class Alerts { this.log.debug(`muting instance ${instanceId} under alert ${id}`); const { data: alert, status, statusText } = await this.axios.post( - `/api/alert/${id}/alert_instance/${instanceId}/_mute` + `/api/alerts/alert/${id}/alert_instance/${instanceId}/_mute` ); if (status !== 204) { throw new Error( diff --git a/x-pack/test/plugin_api_integration/plugins/feature_usage_test/server/plugin.ts b/x-pack/test/plugin_api_integration/plugins/feature_usage_test/server/plugin.ts index b36d6dca077f7..af410d457fc05 100644 --- a/x-pack/test/plugin_api_integration/plugins/feature_usage_test/server/plugin.ts +++ b/x-pack/test/plugin_api_integration/plugins/feature_usage_test/server/plugin.ts @@ -38,9 +38,9 @@ export class FeatureUsageTestPlugin }: CoreSetup, { licensing }: FeatureUsageTestSetupDependencies ) { - licensing.featureUsage.register('test_feature_a'); - licensing.featureUsage.register('test_feature_b'); - licensing.featureUsage.register('test_feature_c'); + licensing.featureUsage.register('Test feature A', 'basic'); + licensing.featureUsage.register('Test feature B', 'gold'); + licensing.featureUsage.register('Test feature C', 'platinum'); registerRoutes(http.createRouter(), getStartServices); diff --git a/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/feature_usage.ts b/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/feature_usage.ts index dfbc41d883e02..5c8fac9586e3a 100644 --- a/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/feature_usage.ts +++ b/x-pack/test/plugin_api_integration/test_suites/licensed_feature_usage/feature_usage.ts @@ -20,15 +20,32 @@ export default function ({ getService }: FtrProviderContext) { describe('/api/licensing/feature_usage', () => { it('returns a map of last feature usages', async () => { const timeA = Date.now(); - await notifyUsage('test_feature_a', timeA); + await notifyUsage('Test feature C', timeA); const timeB = Date.now() - 4567; - await notifyUsage('test_feature_b', timeB); + await notifyUsage('Test feature B', timeB); const response = await supertest.get('/api/licensing/feature_usage').expect(200); - expect(response.body.test_feature_a).to.eql(toISO(timeA)); - expect(response.body.test_feature_b).to.eql(toISO(timeB)); + expect(response.body).to.eql({ + features: [ + { + last_used: null, + license_level: 'basic', + name: 'Test feature A', + }, + { + last_used: toISO(timeB), + license_level: 'gold', + name: 'Test feature B', + }, + { + last_used: toISO(timeA), + license_level: 'platinum', + name: 'Test feature C', + }, + ], + }); }); }); } diff --git a/x-pack/test/siem_cypress/es_archives/signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/alerts/data.json.gz similarity index 100% rename from x-pack/test/siem_cypress/es_archives/signals/data.json.gz rename to x-pack/test/siem_cypress/es_archives/alerts/data.json.gz diff --git a/x-pack/test/siem_cypress/es_archives/signals/mappings.json b/x-pack/test/siem_cypress/es_archives/alerts/mappings.json similarity index 100% rename from x-pack/test/siem_cypress/es_archives/signals/mappings.json rename to x-pack/test/siem_cypress/es_archives/alerts/mappings.json diff --git a/x-pack/test/siem_cypress/es_archives/closed_signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/closed_alerts/data.json.gz similarity index 100% rename from x-pack/test/siem_cypress/es_archives/closed_signals/data.json.gz rename to x-pack/test/siem_cypress/es_archives/closed_alerts/data.json.gz diff --git a/x-pack/test/siem_cypress/es_archives/closed_signals/mappings.json b/x-pack/test/siem_cypress/es_archives/closed_alerts/mappings.json similarity index 100% rename from x-pack/test/siem_cypress/es_archives/closed_signals/mappings.json rename to x-pack/test/siem_cypress/es_archives/closed_alerts/mappings.json diff --git a/x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/timeline_alerts/data.json.gz similarity index 100% rename from x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz rename to x-pack/test/siem_cypress/es_archives/timeline_alerts/data.json.gz diff --git a/x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json b/x-pack/test/siem_cypress/es_archives/timeline_alerts/mappings.json similarity index 100% rename from x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json rename to x-pack/test/siem_cypress/es_archives/timeline_alerts/mappings.json diff --git a/x-pack/typings/hapi.d.ts b/x-pack/typings/hapi.d.ts index ed86a961cd1db..6af723101fc22 100644 --- a/x-pack/typings/hapi.d.ts +++ b/x-pack/typings/hapi.d.ts @@ -9,7 +9,7 @@ import 'hapi'; import { XPackMainPlugin } from '../legacy/plugins/xpack_main/server/xpack_main'; import { SecurityPlugin } from '../legacy/plugins/security'; import { ActionsPlugin, ActionsClient } from '../plugins/actions/server'; -import { AlertingPlugin, AlertsClient } from '../plugins/alerting/server'; +import { AlertingPlugin, AlertsClient } from '../plugins/alerts/server'; import { TaskManager } from '../plugins/task_manager/server'; declare module 'hapi' { @@ -21,7 +21,7 @@ declare module 'hapi' { xpack_main: XPackMainPlugin; security?: SecurityPlugin; actions?: ActionsPlugin; - alerting?: AlertingPlugin; + alerts?: AlertingPlugin; task_manager?: TaskManager; } } diff --git a/yarn.lock b/yarn.lock index 5d47056857bbf..892fa1b5aa567 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1413,10 +1413,10 @@ resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-1.1.1.tgz#619b70322c9cce4a7ee5fbf8f678b1baa7f06095" integrity sha512-F6tIk8Txdqjg8Siv60iAvXzO9ZdQI87K3sS/fh5xd2XaWK+T5ZfqeTvsT7srwG6fr6uCBfuQEJV1KBBl+JpLZA== -"@elastic/numeral@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.4.0.tgz#883197b7f4bf3c2dd994f53b274769ddfa2bf79a" - integrity sha512-uGBKGCNghTgUZPHClji/00v+AKt5nidPTGOIbcT+lbTPVxNB6QPpPLGWtXyrg3QZAxobPM/LAZB1mAqtJeq44Q== +"@elastic/numeral@^2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.5.0.tgz#8da714827fc278f17546601fdfe55f5c920e2bc5" + integrity sha512-NVTuy9Wzblp6nOH86CXjWXTajHgJGn5Tk2l59/Z5cWFU14KlE+8/zqPTgZdxYABzBJFE3L7S07kJDMN8sDvTmA== "@elastic/request-crypto@1.1.4": version "1.1.4" @@ -4546,11 +4546,6 @@ resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-0.0.25.tgz#b6f55062827a4787fe4ab151cf3412a468e65271" integrity sha512-ShHzHkYD+Ldw3eyttptCpUhF1/mkInWwasQkCNXZHOsJMJ/UMa8wXrxSrTJaVk0r4pLK/VnESVM0wFsfQzNEKQ== -"@types/numeral@^0.0.26": - version "0.0.26" - resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-0.0.26.tgz#cfab9842ef9349ce714b06722940ca7ebf8a6298" - integrity sha512-DwCsRqeOWopdEsm5KLTxKVKDSDoj+pzZD1vlwu1GQJ6IF3RhjuleYlRwyRH6MJLGaf3v8wFTnC6wo3yYfz0bnA== - "@types/object-hash@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.0.tgz#b20db2074129f71829d61ff404e618c4ac3d73cf" @@ -4865,9 +4860,9 @@ "@types/sinon" "*" "@types/sinon@*": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.0.tgz#5b70a360f55645dd64f205defd2a31b749a59799" - integrity sha512-v2TkYHkts4VXshMkcmot/H+ERZ2SevKa10saGaJPGCJ8vh3lKrC4u663zYEeRZxep+VbG6YRDtQ6gVqw9dYzPA== + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1" + integrity sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw== dependencies: "@types/sinonjs__fake-timers" "*" @@ -10532,10 +10527,10 @@ cypress-multi-reporters@^1.2.3: debug "^4.1.1" lodash "^4.17.11" -cypress@^4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.4.1.tgz#f5aa1aa5f328f1299bff328103f7cbad89e80f29" - integrity sha512-LcskZ/PXRG9XTlEeeenKqz/KddT1x+7O7dqXsdKWPII01LxLNmNHIvHnlUqApchVbinJ5vir6J255CkELSeL0A== +cypress@4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.5.0.tgz#01940d085f6429cec3c87d290daa47bb976a7c7b" + integrity sha512-2A4g5FW5d2fHzq8HKUGAMVTnW6P8nlWYQALiCoGN4bqBLvgwhYM/oG9oKc2CS6LnvgHFiKivKzpm9sfk3uU3zQ== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" "@cypress/request" "2.88.5"